test_restore.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345
  1. import pytest
  2. from flexmock import flexmock
  3. import borgmatic.actions.restore as module
  4. @pytest.mark.parametrize(
  5. 'first_dump,second_dump,default_port,expected_result',
  6. (
  7. (
  8. module.Dump('postgresql_databases', 'foo'),
  9. module.Dump('postgresql_databases', 'foo'),
  10. None,
  11. True,
  12. ),
  13. (
  14. module.Dump('postgresql_databases', 'foo'),
  15. module.Dump('postgresql_databases', 'bar'),
  16. None,
  17. False,
  18. ),
  19. (
  20. module.Dump('postgresql_databases', 'foo'),
  21. module.Dump('mariadb_databases', 'foo'),
  22. None,
  23. False,
  24. ),
  25. (
  26. module.Dump('postgresql_databases', 'foo'),
  27. module.Dump(module.UNSPECIFIED, 'foo'),
  28. None,
  29. True,
  30. ),
  31. (
  32. module.Dump('postgresql_databases', 'foo'),
  33. module.Dump(module.UNSPECIFIED, 'bar'),
  34. None,
  35. False,
  36. ),
  37. (
  38. module.Dump('postgresql_databases', module.UNSPECIFIED),
  39. module.Dump('postgresql_databases', 'foo'),
  40. None,
  41. True,
  42. ),
  43. (
  44. module.Dump('postgresql_databases', module.UNSPECIFIED),
  45. module.Dump('mariadb_databases', 'foo'),
  46. None,
  47. False,
  48. ),
  49. (
  50. module.Dump('postgresql_databases', 'foo', 'myhost'),
  51. module.Dump('postgresql_databases', 'foo', 'myhost'),
  52. None,
  53. True,
  54. ),
  55. (
  56. module.Dump('postgresql_databases', 'foo', 'myhost'),
  57. module.Dump('postgresql_databases', 'foo', 'otherhost'),
  58. None,
  59. False,
  60. ),
  61. (
  62. module.Dump('postgresql_databases', 'foo', 'myhost'),
  63. module.Dump('postgresql_databases', 'foo', module.UNSPECIFIED),
  64. None,
  65. True,
  66. ),
  67. (
  68. module.Dump('postgresql_databases', 'foo', 'myhost'),
  69. module.Dump('postgresql_databases', 'bar', module.UNSPECIFIED),
  70. None,
  71. False,
  72. ),
  73. (
  74. module.Dump('postgresql_databases', 'foo', 'myhost', 1234),
  75. module.Dump('postgresql_databases', 'foo', 'myhost', 1234),
  76. None,
  77. True,
  78. ),
  79. (
  80. module.Dump('postgresql_databases', 'foo', 'myhost', 1234),
  81. module.Dump('postgresql_databases', 'foo', 'myhost', 4321),
  82. None,
  83. False,
  84. ),
  85. (
  86. module.Dump('postgresql_databases', 'foo', 'myhost', module.UNSPECIFIED),
  87. module.Dump('postgresql_databases', 'foo', 'myhost', 1234),
  88. None,
  89. True,
  90. ),
  91. (
  92. module.Dump('postgresql_databases', 'foo', 'myhost', module.UNSPECIFIED),
  93. module.Dump('postgresql_databases', 'foo', 'otherhost', 1234),
  94. None,
  95. False,
  96. ),
  97. (
  98. module.Dump(
  99. module.UNSPECIFIED,
  100. module.UNSPECIFIED,
  101. module.UNSPECIFIED,
  102. module.UNSPECIFIED,
  103. ),
  104. module.Dump('postgresql_databases', 'foo', 'myhost', 1234),
  105. None,
  106. True,
  107. ),
  108. (
  109. module.Dump('postgresql_databases', 'foo', 'myhost', 5432),
  110. module.Dump('postgresql_databases', 'foo', 'myhost', None),
  111. 5432,
  112. True,
  113. ),
  114. (
  115. module.Dump('postgresql_databases', 'foo', 'myhost', None),
  116. module.Dump('postgresql_databases', 'foo', 'myhost', 5432),
  117. 5432,
  118. True,
  119. ),
  120. (
  121. module.Dump('postgresql_databases', 'foo', 'myhost', 5433),
  122. module.Dump('postgresql_databases', 'foo', 'myhost', None),
  123. 5432,
  124. False,
  125. ),
  126. ),
  127. )
  128. def test_dumps_match_compares_two_dumps_while_respecting_unspecified_values(
  129. first_dump,
  130. second_dump,
  131. default_port,
  132. expected_result,
  133. ):
  134. assert module.dumps_match(first_dump, second_dump, default_port) == expected_result
  135. @pytest.mark.parametrize(
  136. 'dump,expected_result',
  137. (
  138. (
  139. module.Dump('postgresql_databases', 'foo'),
  140. 'foo@localhost (postgresql_databases)',
  141. ),
  142. (
  143. module.Dump(module.UNSPECIFIED, 'foo'),
  144. 'foo@localhost',
  145. ),
  146. (
  147. module.Dump('postgresql_databases', module.UNSPECIFIED),
  148. 'unspecified@localhost (postgresql_databases)',
  149. ),
  150. (
  151. module.Dump('postgresql_databases', 'foo', 'host'),
  152. 'foo@host (postgresql_databases)',
  153. ),
  154. (
  155. module.Dump('postgresql_databases', 'foo', module.UNSPECIFIED),
  156. 'foo (postgresql_databases)',
  157. ),
  158. (
  159. module.Dump('postgresql_databases', 'foo', 'host', 1234),
  160. 'foo@host:1234 (postgresql_databases)',
  161. ),
  162. (
  163. module.Dump('postgresql_databases', 'foo', module.UNSPECIFIED, 1234),
  164. 'foo@:1234 (postgresql_databases)',
  165. ),
  166. (
  167. module.Dump('postgresql_databases', 'foo', 'host', module.UNSPECIFIED),
  168. 'foo@host (postgresql_databases)',
  169. ),
  170. (
  171. module.Dump(
  172. module.UNSPECIFIED,
  173. module.UNSPECIFIED,
  174. module.UNSPECIFIED,
  175. module.UNSPECIFIED,
  176. ),
  177. 'unspecified',
  178. ),
  179. ),
  180. )
  181. def test_render_dump_metadata_renders_dump_values_into_string(dump, expected_result):
  182. assert module.render_dump_metadata(dump) == expected_result
  183. def test_get_configured_data_source_matches_data_source_with_restore_dump():
  184. default_port = flexmock()
  185. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').and_return(default_port)
  186. flexmock(module).should_receive('dumps_match').and_return(False)
  187. flexmock(module).should_receive('dumps_match').with_args(
  188. module.Dump('postgresql_databases', 'bar'),
  189. module.Dump('postgresql_databases', 'bar'),
  190. default_port=default_port,
  191. ).and_return(True)
  192. assert module.get_configured_data_source(
  193. config={
  194. 'other_databases': [{'name': 'other'}],
  195. 'postgresql_databases': [{'name': 'foo'}, {'name': 'bar'}],
  196. },
  197. restore_dump=module.Dump('postgresql_databases', 'bar'),
  198. ) == {'name': 'bar'}
  199. def test_get_configured_data_source_matches_nothing_when_nothing_configured():
  200. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').and_return(flexmock())
  201. flexmock(module).should_receive('dumps_match').and_return(False)
  202. assert (
  203. module.get_configured_data_source(
  204. config={},
  205. restore_dump=module.Dump('postgresql_databases', 'quux'),
  206. )
  207. is None
  208. )
  209. def test_get_configured_data_source_matches_nothing_when_restore_dump_does_not_match_configuration():
  210. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').and_return(flexmock())
  211. flexmock(module).should_receive('dumps_match').and_return(False)
  212. assert (
  213. module.get_configured_data_source(
  214. config={
  215. 'postgresql_databases': [{'name': 'foo'}],
  216. },
  217. restore_dump=module.Dump('postgresql_databases', 'quux'),
  218. )
  219. is None
  220. )
  221. def test_get_configured_data_source_with_multiple_matching_data_sources_errors():
  222. default_port = flexmock()
  223. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').and_return(default_port)
  224. flexmock(module).should_receive('dumps_match').and_return(False)
  225. flexmock(module).should_receive('dumps_match').with_args(
  226. module.Dump('postgresql_databases', 'bar'),
  227. module.Dump('postgresql_databases', 'bar'),
  228. default_port=default_port,
  229. ).and_return(True)
  230. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  231. with pytest.raises(ValueError):
  232. module.get_configured_data_source(
  233. config={
  234. 'other_databases': [{'name': 'other'}],
  235. 'postgresql_databases': [
  236. {'name': 'foo'},
  237. {'name': 'bar'},
  238. {'name': 'bar', 'format': 'directory'},
  239. ],
  240. },
  241. restore_dump=module.Dump('postgresql_databases', 'bar'),
  242. )
  243. def test_strip_path_prefix_from_extracted_dump_destination_renames_first_matching_databases_subdirectory():
  244. flexmock(module.os).should_receive('walk').and_return(
  245. [
  246. ('/foo', flexmock(), flexmock()),
  247. ('/foo/bar', flexmock(), flexmock()),
  248. ('/foo/bar/postgresql_databases', flexmock(), flexmock()),
  249. ('/foo/bar/mariadb_databases', flexmock(), flexmock()),
  250. ],
  251. )
  252. flexmock(module.shutil).should_receive('move').with_args(
  253. '/foo/bar/postgresql_databases',
  254. '/run/user/0/borgmatic/postgresql_databases',
  255. ).once()
  256. flexmock(module.shutil).should_receive('move').with_args(
  257. '/foo/bar/mariadb_databases',
  258. '/run/user/0/borgmatic/mariadb_databases',
  259. ).never()
  260. module.strip_path_prefix_from_extracted_dump_destination('/foo', '/run/user/0/borgmatic')
  261. def test_restore_single_dump_extracts_and_restores_single_file_dump():
  262. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  263. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  264. 'make_data_source_dump_patterns',
  265. object,
  266. object,
  267. object,
  268. object,
  269. ).and_return({'postgresql': flexmock()})
  270. flexmock(module.tempfile).should_receive('mkdtemp').never()
  271. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  272. 'convert_glob_patterns_to_borg_pattern',
  273. ).and_return(flexmock())
  274. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
  275. flexmock(),
  276. ).once()
  277. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').never()
  278. flexmock(module.shutil).should_receive('rmtree').never()
  279. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  280. function_name='restore_data_source_dump',
  281. config=object,
  282. hook_name=object,
  283. data_source=object,
  284. dry_run=object,
  285. extract_process=object,
  286. connection_params=object,
  287. borgmatic_runtime_directory=object,
  288. ).once()
  289. module.restore_single_dump(
  290. repository={'path': 'test.borg'},
  291. config=flexmock(),
  292. local_borg_version=flexmock(),
  293. global_arguments=flexmock(dry_run=False),
  294. local_path=None,
  295. remote_path=None,
  296. archive_name=flexmock(),
  297. hook_name='postgresql',
  298. data_source={'name': 'test', 'format': 'plain'},
  299. connection_params=flexmock(),
  300. borgmatic_runtime_directory='/run/borgmatic',
  301. )
  302. def test_restore_single_dump_extracts_and_restores_directory_dump():
  303. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  304. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  305. 'make_data_source_dump_patterns',
  306. object,
  307. object,
  308. object,
  309. object,
  310. ).and_return({'postgresql': flexmock()})
  311. flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
  312. '/run/user/0/borgmatic/tmp1234',
  313. )
  314. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  315. 'convert_glob_patterns_to_borg_pattern',
  316. ).and_return(flexmock())
  317. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
  318. flexmock(),
  319. ).once()
  320. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').once()
  321. flexmock(module.shutil).should_receive('rmtree').once()
  322. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  323. function_name='restore_data_source_dump',
  324. config=object,
  325. hook_name=object,
  326. data_source=object,
  327. dry_run=object,
  328. extract_process=object,
  329. connection_params=object,
  330. borgmatic_runtime_directory='/run/borgmatic',
  331. ).once()
  332. module.restore_single_dump(
  333. repository={'path': 'test.borg'},
  334. config=flexmock(),
  335. local_borg_version=flexmock(),
  336. global_arguments=flexmock(dry_run=False),
  337. local_path=None,
  338. remote_path=None,
  339. archive_name=flexmock(),
  340. hook_name='postgresql',
  341. data_source={'name': 'test', 'format': 'directory'},
  342. connection_params=flexmock(),
  343. borgmatic_runtime_directory='/run/borgmatic',
  344. )
  345. def test_restore_single_dump_with_directory_dump_error_cleans_up_temporary_directory():
  346. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  347. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  348. 'make_data_source_dump_patterns',
  349. object,
  350. object,
  351. object,
  352. object,
  353. ).and_return({'postgresql': flexmock()})
  354. flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
  355. '/run/user/0/borgmatic/tmp1234',
  356. )
  357. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  358. 'convert_glob_patterns_to_borg_pattern',
  359. ).and_return(flexmock())
  360. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_raise(
  361. ValueError,
  362. ).once()
  363. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').never()
  364. flexmock(module.shutil).should_receive('rmtree').once()
  365. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  366. function_name='restore_data_source_dump',
  367. config=object,
  368. hook_name=object,
  369. data_source=object,
  370. dry_run=object,
  371. extract_process=object,
  372. connection_params=object,
  373. borgmatic_runtime_directory='/run/user/0/borgmatic/tmp1234',
  374. ).never()
  375. with pytest.raises(ValueError):
  376. module.restore_single_dump(
  377. repository={'path': 'test.borg'},
  378. config=flexmock(),
  379. local_borg_version=flexmock(),
  380. global_arguments=flexmock(dry_run=False),
  381. local_path=None,
  382. remote_path=None,
  383. archive_name=flexmock(),
  384. hook_name='postgresql',
  385. data_source={'name': 'test', 'format': 'directory'},
  386. connection_params=flexmock(),
  387. borgmatic_runtime_directory='/run/borgmatic',
  388. )
  389. def test_restore_single_dump_with_directory_dump_and_dry_run_skips_directory_move_and_cleanup():
  390. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  391. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  392. 'make_data_source_dump_patterns',
  393. object,
  394. object,
  395. object,
  396. object,
  397. ).and_return({'postgresql': flexmock()})
  398. flexmock(module.tempfile).should_receive('mkdtemp').once().and_return('/run/borgmatic/tmp1234')
  399. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  400. 'convert_glob_patterns_to_borg_pattern',
  401. ).and_return(flexmock())
  402. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
  403. flexmock(),
  404. ).once()
  405. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').never()
  406. flexmock(module.shutil).should_receive('rmtree').never()
  407. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  408. function_name='restore_data_source_dump',
  409. config=object,
  410. hook_name=object,
  411. data_source=object,
  412. dry_run=object,
  413. extract_process=object,
  414. connection_params=object,
  415. borgmatic_runtime_directory='/run/borgmatic',
  416. ).once()
  417. module.restore_single_dump(
  418. repository={'path': 'test.borg'},
  419. config=flexmock(),
  420. local_borg_version=flexmock(),
  421. global_arguments=flexmock(dry_run=True),
  422. local_path=None,
  423. remote_path=None,
  424. archive_name=flexmock(),
  425. hook_name='postgresql',
  426. data_source={'name': 'test', 'format': 'directory'},
  427. connection_params=flexmock(),
  428. borgmatic_runtime_directory='/run/borgmatic',
  429. )
  430. def test_collect_dumps_from_archive_with_dumps_metadata_parses_it():
  431. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  432. 'make_data_source_dump_path',
  433. ).and_return('')
  434. flexmock(module.borgmatic.config.paths).should_receive('make_runtime_directory_glob').and_return('')
  435. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  436. [
  437. 'borgmatic/postgresql_databases/dumps.json',
  438. 'borgmatic/mysql_databases/dumps.json',
  439. ],
  440. )
  441. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(flexmock(stdout=flexmock(read=lambda: b'')))
  442. dumps_metadata = [
  443. module.Dump('postgresql_databases', 'foo'),
  444. module.Dump('postgresql_databases', 'bar', 'host', 1234),
  445. module.Dump('mysql_databases', 'quux'),
  446. ]
  447. flexmock(module.borgmatic.hooks.data_source.dump).should_receive('parse_data_source_dumps_metadata').and_return(dumps_metadata)
  448. flexmock(module.borgmatic.config.paths).should_receive('get_borgmatic_source_directory').never()
  449. archive_dumps = module.collect_dumps_from_archive(
  450. repository={'path': 'repo'},
  451. archive='archive',
  452. config={},
  453. local_borg_version=flexmock(),
  454. global_arguments=flexmock(dry_run=False, log_json=False),
  455. local_path=flexmock(),
  456. remote_path=flexmock(),
  457. borgmatic_runtime_directory='/run/borgmatic',
  458. )
  459. assert archive_dumps == set(dumps_metadata)
  460. def test_collect_dumps_from_archive_without_dumps_metadata_falls_back_to_parsing_archive_paths():
  461. flexmock(module.borgmatic.config.paths).should_receive('make_runtime_directory_glob').and_return('')
  462. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return([]).and_return(
  463. [
  464. 'borgmatic/postgresql_databases/localhost/foo',
  465. 'borgmatic/postgresql_databases/host:1234/bar',
  466. 'borgmatic/mysql_databases/localhost/quux',
  467. ],
  468. )
  469. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').never()
  470. flexmock(module.borgmatic.hooks.data_source.dump).should_receive('parse_data_source_dumps_metadata').never()
  471. flexmock(module.borgmatic.config.paths).should_receive(
  472. 'get_borgmatic_source_directory',
  473. ).and_return('/root/.borgmatic')
  474. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  475. 'make_data_source_dump_path',
  476. ).and_return('')
  477. archive_dumps = module.collect_dumps_from_archive(
  478. repository={'path': 'repo'},
  479. archive='archive',
  480. config={},
  481. local_borg_version=flexmock(),
  482. global_arguments=flexmock(dry_run=False, log_json=False),
  483. local_path=flexmock(),
  484. remote_path=flexmock(),
  485. borgmatic_runtime_directory='/run/borgmatic',
  486. )
  487. assert archive_dumps == {
  488. module.Dump('postgresql_databases', 'foo'),
  489. module.Dump('postgresql_databases', 'bar', 'host', 1234),
  490. module.Dump('mysql_databases', 'quux'),
  491. }
  492. def test_collect_dumps_from_archive_parses_archive_paths_with_different_base_directories():
  493. flexmock(module.borgmatic.config.paths).should_receive('make_runtime_directory_glob').and_return('')
  494. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return([]).and_return(
  495. [
  496. 'borgmatic/postgresql_databases/localhost/foo',
  497. '.borgmatic/postgresql_databases/localhost/bar',
  498. '/root/.borgmatic/postgresql_databases/localhost/baz',
  499. '/var/run/0/borgmatic/mysql_databases/localhost/quux',
  500. ],
  501. )
  502. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').never()
  503. flexmock(module.borgmatic.hooks.data_source.dump).should_receive('parse_data_source_dumps_metadata').never()
  504. flexmock(module.borgmatic.config.paths).should_receive(
  505. 'get_borgmatic_source_directory',
  506. ).and_return('/root/.borgmatic')
  507. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  508. 'make_data_source_dump_path',
  509. ).and_return('')
  510. archive_dumps = module.collect_dumps_from_archive(
  511. repository={'path': 'repo'},
  512. archive='archive',
  513. config={},
  514. local_borg_version=flexmock(),
  515. global_arguments=flexmock(log_json=False),
  516. local_path=flexmock(),
  517. remote_path=flexmock(),
  518. borgmatic_runtime_directory='/run/borgmatic',
  519. )
  520. assert archive_dumps == {
  521. module.Dump('postgresql_databases', 'foo'),
  522. module.Dump('postgresql_databases', 'bar'),
  523. module.Dump('postgresql_databases', 'baz'),
  524. module.Dump('mysql_databases', 'quux'),
  525. }
  526. def test_collect_dumps_from_archive_parses_directory_format_archive_paths():
  527. flexmock(module.borgmatic.config.paths).should_receive('make_runtime_directory_glob').and_return('')
  528. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return([]).and_return(
  529. [
  530. 'borgmatic/postgresql_databases/localhost/foo/table1',
  531. 'borgmatic/postgresql_databases/localhost/foo/table2',
  532. ],
  533. )
  534. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').never()
  535. flexmock(module.borgmatic.hooks.data_source.dump).should_receive('parse_data_source_dumps_metadata').never()
  536. flexmock(module.borgmatic.config.paths).should_receive(
  537. 'get_borgmatic_source_directory',
  538. ).and_return('/root/.borgmatic')
  539. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  540. 'make_data_source_dump_path',
  541. ).and_return('')
  542. archive_dumps = module.collect_dumps_from_archive(
  543. repository={'path': 'repo'},
  544. archive='archive',
  545. config={},
  546. local_borg_version=flexmock(),
  547. global_arguments=flexmock(log_json=False),
  548. local_path=flexmock(),
  549. remote_path=flexmock(),
  550. borgmatic_runtime_directory='/run/borgmatic',
  551. )
  552. assert archive_dumps == {
  553. module.Dump('postgresql_databases', 'foo'),
  554. }
  555. def test_collect_dumps_from_archive_skips_bad_archive_paths_or_bad_path_components():
  556. flexmock(module.borgmatic.config.paths).should_receive('make_runtime_directory_glob').and_return('')
  557. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return([]).and_return(
  558. [
  559. 'borgmatic/postgresql_databases/localhost/foo',
  560. 'borgmatic/postgresql_databases/localhost:abcd/bar',
  561. 'borgmatic/invalid',
  562. 'invalid/as/well',
  563. '',
  564. ],
  565. )
  566. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').never()
  567. flexmock(module.borgmatic.hooks.data_source.dump).should_receive('parse_data_source_dumps_metadata').never()
  568. flexmock(module.borgmatic.config.paths).should_receive(
  569. 'get_borgmatic_source_directory',
  570. ).and_return('/root/.borgmatic')
  571. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  572. 'make_data_source_dump_path',
  573. ).and_return('')
  574. archive_dumps = module.collect_dumps_from_archive(
  575. repository={'path': 'repo'},
  576. archive='archive',
  577. config={},
  578. local_borg_version=flexmock(),
  579. global_arguments=flexmock(log_json=False),
  580. local_path=flexmock(),
  581. remote_path=flexmock(),
  582. borgmatic_runtime_directory='/run/borgmatic',
  583. )
  584. assert archive_dumps == {
  585. module.Dump('postgresql_databases', 'foo'),
  586. module.Dump('postgresql_databases', 'bar'),
  587. }
  588. def test_get_dumps_to_restore_gets_requested_dumps_found_in_archive():
  589. dumps_from_archive = {
  590. module.Dump('postgresql_databases', 'foo'),
  591. module.Dump('postgresql_databases', 'bar'),
  592. module.Dump('postgresql_databases', 'baz'),
  593. }
  594. flexmock(module).should_receive('dumps_match').and_return(False)
  595. flexmock(module).should_receive('dumps_match').with_args(
  596. module.Dump(module.UNSPECIFIED, 'foo', hostname=module.UNSPECIFIED),
  597. module.Dump('postgresql_databases', 'foo'),
  598. ).and_return(True)
  599. flexmock(module).should_receive('dumps_match').with_args(
  600. module.Dump(module.UNSPECIFIED, 'bar', hostname=module.UNSPECIFIED),
  601. module.Dump('postgresql_databases', 'bar'),
  602. ).and_return(True)
  603. assert module.get_dumps_to_restore(
  604. restore_arguments=flexmock(
  605. hook=None,
  606. data_sources=['foo', 'bar'],
  607. original_hostname=None,
  608. original_port=None,
  609. ),
  610. dumps_from_archive=dumps_from_archive,
  611. ) == {
  612. module.Dump('postgresql_databases', 'foo'),
  613. module.Dump('postgresql_databases', 'bar'),
  614. }
  615. def test_get_dumps_to_restore_raises_for_requested_dumps_missing_from_archive():
  616. dumps_from_archive = {
  617. module.Dump('postgresql_databases', 'foo'),
  618. }
  619. flexmock(module).should_receive('dumps_match').and_return(False)
  620. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  621. with pytest.raises(ValueError):
  622. module.get_dumps_to_restore(
  623. restore_arguments=flexmock(
  624. hook=None,
  625. data_sources=['foo', 'bar'],
  626. original_hostname=None,
  627. original_port=None,
  628. ),
  629. dumps_from_archive=dumps_from_archive,
  630. )
  631. def test_get_dumps_to_restore_without_requested_dumps_finds_all_archive_dumps():
  632. dumps_from_archive = {
  633. module.Dump('postgresql_databases', 'foo'),
  634. module.Dump('postgresql_databases', 'bar'),
  635. }
  636. flexmock(module).should_receive('dumps_match').and_return(False)
  637. assert (
  638. module.get_dumps_to_restore(
  639. restore_arguments=flexmock(
  640. hook=None,
  641. data_sources=[],
  642. original_hostname=None,
  643. original_port=None,
  644. ),
  645. dumps_from_archive=dumps_from_archive,
  646. )
  647. == dumps_from_archive
  648. )
  649. def test_get_dumps_to_restore_with_all_in_requested_dumps_finds_all_archive_dumps():
  650. dumps_from_archive = {
  651. module.Dump('postgresql_databases', 'foo'),
  652. module.Dump('postgresql_databases', 'bar'),
  653. }
  654. flexmock(module).should_receive('dumps_match').and_return(False)
  655. flexmock(module).should_receive('dumps_match').with_args(
  656. module.Dump(module.UNSPECIFIED, 'foo', hostname=module.UNSPECIFIED),
  657. module.Dump('postgresql_databases', 'foo'),
  658. ).and_return(True)
  659. flexmock(module).should_receive('dumps_match').with_args(
  660. module.Dump(module.UNSPECIFIED, 'bar', hostname=module.UNSPECIFIED),
  661. module.Dump('postgresql_databases', 'bar'),
  662. ).and_return(True)
  663. assert (
  664. module.get_dumps_to_restore(
  665. restore_arguments=flexmock(
  666. hook=None,
  667. data_sources=['all'],
  668. original_hostname=None,
  669. original_port=None,
  670. ),
  671. dumps_from_archive=dumps_from_archive,
  672. )
  673. == dumps_from_archive
  674. )
  675. def test_get_dumps_to_restore_with_all_in_requested_dumps_plus_additional_requested_dumps_omits_duplicates():
  676. dumps_from_archive = {
  677. module.Dump('postgresql_databases', 'foo'),
  678. module.Dump('postgresql_databases', 'bar'),
  679. }
  680. flexmock(module).should_receive('dumps_match').and_return(False)
  681. flexmock(module).should_receive('dumps_match').with_args(
  682. module.Dump(module.UNSPECIFIED, 'foo', hostname=module.UNSPECIFIED),
  683. module.Dump('postgresql_databases', 'foo'),
  684. ).and_return(True)
  685. flexmock(module).should_receive('dumps_match').with_args(
  686. module.Dump(module.UNSPECIFIED, 'bar', hostname=module.UNSPECIFIED),
  687. module.Dump('postgresql_databases', 'bar'),
  688. ).and_return(True)
  689. assert (
  690. module.get_dumps_to_restore(
  691. restore_arguments=flexmock(
  692. hook=None,
  693. data_sources=['all', 'foo', 'bar'],
  694. original_hostname=None,
  695. original_port=None,
  696. ),
  697. dumps_from_archive=dumps_from_archive,
  698. )
  699. == dumps_from_archive
  700. )
  701. def test_get_dumps_to_restore_raises_for_multiple_matching_dumps_in_archive():
  702. flexmock(module).should_receive('dumps_match').and_return(False)
  703. flexmock(module).should_receive('dumps_match').with_args(
  704. module.Dump(module.UNSPECIFIED, 'foo', hostname=module.UNSPECIFIED),
  705. module.Dump('postgresql_databases', 'foo'),
  706. ).and_return(True)
  707. flexmock(module).should_receive('dumps_match').with_args(
  708. module.Dump(module.UNSPECIFIED, 'foo', hostname=module.UNSPECIFIED),
  709. module.Dump('mariadb_databases', 'foo'),
  710. ).and_return(True)
  711. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  712. with pytest.raises(ValueError):
  713. module.get_dumps_to_restore(
  714. restore_arguments=flexmock(
  715. hook=None,
  716. data_sources=['foo'],
  717. original_hostname=None,
  718. original_port=None,
  719. ),
  720. dumps_from_archive={
  721. module.Dump('postgresql_databases', 'foo'),
  722. module.Dump('mariadb_databases', 'foo'),
  723. },
  724. )
  725. def test_get_dumps_to_restore_raises_for_all_in_requested_dumps_and_requested_dumps_missing_from_archive():
  726. flexmock(module).should_receive('dumps_match').and_return(False)
  727. flexmock(module).should_receive('dumps_match').with_args(
  728. module.Dump(module.UNSPECIFIED, 'foo', hostname=module.UNSPECIFIED),
  729. module.Dump('postgresql_databases', 'foo'),
  730. ).and_return(True)
  731. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  732. with pytest.raises(ValueError):
  733. module.get_dumps_to_restore(
  734. restore_arguments=flexmock(
  735. hook=None,
  736. data_sources=['all', 'foo', 'bar'],
  737. original_hostname=None,
  738. original_port=None,
  739. ),
  740. dumps_from_archive={module.Dump('postresql_databases', 'foo')},
  741. )
  742. def test_get_dumps_to_restore_with_requested_hook_name_filters_dumps_found_in_archive():
  743. dumps_from_archive = {
  744. module.Dump('mariadb_databases', 'foo'),
  745. module.Dump('postgresql_databases', 'foo'),
  746. module.Dump('sqlite_databases', 'bar'),
  747. }
  748. flexmock(module).should_receive('dumps_match').and_return(False)
  749. flexmock(module).should_receive('dumps_match').with_args(
  750. module.Dump('postgresql_databases', 'foo', hostname=module.UNSPECIFIED),
  751. module.Dump('postgresql_databases', 'foo'),
  752. ).and_return(True)
  753. assert module.get_dumps_to_restore(
  754. restore_arguments=flexmock(
  755. hook='postgresql_databases',
  756. data_sources=['foo'],
  757. original_hostname=None,
  758. original_port=None,
  759. ),
  760. dumps_from_archive=dumps_from_archive,
  761. ) == {
  762. module.Dump('postgresql_databases', 'foo'),
  763. }
  764. def test_get_dumps_to_restore_with_requested_shortened_hook_name_filters_dumps_found_in_archive():
  765. dumps_from_archive = {
  766. module.Dump('mariadb_databases', 'foo'),
  767. module.Dump('postgresql_databases', 'foo'),
  768. module.Dump('sqlite_databases', 'bar'),
  769. }
  770. flexmock(module).should_receive('dumps_match').and_return(False)
  771. flexmock(module).should_receive('dumps_match').with_args(
  772. module.Dump('postgresql_databases', 'foo', hostname=module.UNSPECIFIED),
  773. module.Dump('postgresql_databases', 'foo'),
  774. ).and_return(True)
  775. assert module.get_dumps_to_restore(
  776. restore_arguments=flexmock(
  777. hook='postgresql',
  778. data_sources=['foo'],
  779. original_hostname=None,
  780. original_port=None,
  781. ),
  782. dumps_from_archive=dumps_from_archive,
  783. ) == {
  784. module.Dump('postgresql_databases', 'foo'),
  785. }
  786. def test_get_dumps_to_restore_with_requested_hostname_filters_dumps_found_in_archive():
  787. dumps_from_archive = {
  788. module.Dump('postgresql_databases', 'foo'),
  789. module.Dump('postgresql_databases', 'foo', 'host'),
  790. module.Dump('postgresql_databases', 'bar'),
  791. }
  792. flexmock(module).should_receive('dumps_match').and_return(False)
  793. flexmock(module).should_receive('dumps_match').with_args(
  794. module.Dump('postgresql_databases', 'foo', 'host'),
  795. module.Dump('postgresql_databases', 'foo', 'host'),
  796. ).and_return(True)
  797. assert module.get_dumps_to_restore(
  798. restore_arguments=flexmock(
  799. hook='postgresql_databases',
  800. data_sources=['foo'],
  801. original_hostname='host',
  802. original_port=None,
  803. ),
  804. dumps_from_archive=dumps_from_archive,
  805. ) == {
  806. module.Dump('postgresql_databases', 'foo', 'host'),
  807. }
  808. def test_get_dumps_to_restore_with_requested_port_filters_dumps_found_in_archive():
  809. dumps_from_archive = {
  810. module.Dump('postgresql_databases', 'foo', 'host'),
  811. module.Dump('postgresql_databases', 'foo', 'host', 1234),
  812. module.Dump('postgresql_databases', 'bar'),
  813. }
  814. flexmock(module).should_receive('dumps_match').and_return(False)
  815. flexmock(module).should_receive('dumps_match').with_args(
  816. module.Dump('postgresql_databases', 'foo', 'host', 1234),
  817. module.Dump('postgresql_databases', 'foo', 'host', 1234),
  818. ).and_return(True)
  819. assert module.get_dumps_to_restore(
  820. restore_arguments=flexmock(
  821. hook='postgresql_databases',
  822. data_sources=['foo'],
  823. original_hostname='host',
  824. original_port=1234,
  825. ),
  826. dumps_from_archive=dumps_from_archive,
  827. ) == {
  828. module.Dump('postgresql_databases', 'foo', 'host', 1234),
  829. }
  830. def test_ensure_requested_dumps_restored_with_all_dumps_restored_does_not_raise():
  831. module.ensure_requested_dumps_restored(
  832. dumps_to_restore={
  833. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  834. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  835. },
  836. dumps_actually_restored={
  837. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  838. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  839. },
  840. )
  841. def test_ensure_requested_dumps_restored_with_no_dumps_raises():
  842. with pytest.raises(ValueError):
  843. module.ensure_requested_dumps_restored(
  844. dumps_to_restore={},
  845. dumps_actually_restored={},
  846. )
  847. def test_ensure_requested_dumps_restored_with_missing_dumps_raises():
  848. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  849. with pytest.raises(ValueError):
  850. module.ensure_requested_dumps_restored(
  851. dumps_to_restore={
  852. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  853. },
  854. dumps_actually_restored={
  855. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  856. },
  857. )
  858. def test_run_restore_restores_each_data_source():
  859. dumps_to_restore = {
  860. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  861. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  862. }
  863. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  864. borgmatic_runtime_directory = flexmock()
  865. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  866. borgmatic_runtime_directory,
  867. )
  868. flexmock(module.borgmatic.config.paths).should_receive(
  869. 'make_runtime_directory_glob',
  870. ).replace_with(lambda path: path)
  871. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  872. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  873. flexmock(),
  874. )
  875. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  876. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  877. flexmock(module).should_receive('get_configured_data_source').and_return(
  878. {'name': 'foo'},
  879. ).and_return({'name': 'bar'})
  880. flexmock(module).should_receive('restore_single_dump').with_args(
  881. repository=object,
  882. config=object,
  883. local_borg_version=object,
  884. global_arguments=object,
  885. local_path=object,
  886. remote_path=object,
  887. archive_name=object,
  888. hook_name='postgresql_databases',
  889. data_source={'name': 'foo', 'schemas': None},
  890. connection_params=object,
  891. borgmatic_runtime_directory=borgmatic_runtime_directory,
  892. ).once()
  893. flexmock(module).should_receive('restore_single_dump').with_args(
  894. repository=object,
  895. config=object,
  896. local_borg_version=object,
  897. global_arguments=object,
  898. local_path=object,
  899. remote_path=object,
  900. archive_name=object,
  901. hook_name='postgresql_databases',
  902. data_source={'name': 'bar', 'schemas': None},
  903. connection_params=object,
  904. borgmatic_runtime_directory=borgmatic_runtime_directory,
  905. ).once()
  906. flexmock(module).should_receive('ensure_requested_dumps_restored')
  907. module.run_restore(
  908. repository={'path': 'repo'},
  909. config=flexmock(),
  910. local_borg_version=flexmock(),
  911. restore_arguments=flexmock(
  912. repository='repo',
  913. archive='archive',
  914. data_sources=flexmock(),
  915. schemas=None,
  916. hostname=None,
  917. port=None,
  918. username=None,
  919. password=None,
  920. restore_path=None,
  921. ),
  922. global_arguments=flexmock(dry_run=False),
  923. local_path=flexmock(),
  924. remote_path=flexmock(),
  925. )
  926. def test_run_restore_bails_for_non_matching_repository():
  927. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(
  928. False,
  929. )
  930. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  931. flexmock(),
  932. )
  933. flexmock(module.borgmatic.config.paths).should_receive(
  934. 'make_runtime_directory_glob',
  935. ).replace_with(lambda path: path)
  936. flexmock(module.borgmatic.hooks.dispatch).should_receive(
  937. 'call_hooks_even_if_unconfigured',
  938. ).never()
  939. flexmock(module).should_receive('restore_single_dump').never()
  940. module.run_restore(
  941. repository={'path': 'repo'},
  942. config=flexmock(),
  943. local_borg_version=flexmock(),
  944. restore_arguments=flexmock(repository='repo', archive='archive', data_sources=flexmock()),
  945. global_arguments=flexmock(dry_run=False),
  946. local_path=flexmock(),
  947. remote_path=flexmock(),
  948. )
  949. def test_run_restore_restores_data_source_by_falling_back_to_all_name():
  950. dumps_to_restore = {
  951. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  952. }
  953. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  954. borgmatic_runtime_directory = flexmock()
  955. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  956. borgmatic_runtime_directory,
  957. )
  958. flexmock(module.borgmatic.config.paths).should_receive(
  959. 'make_runtime_directory_glob',
  960. ).replace_with(lambda path: path)
  961. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  962. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  963. flexmock(),
  964. )
  965. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  966. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  967. flexmock(module).should_receive('get_configured_data_source').and_return(
  968. {'name': 'foo'},
  969. ).and_return({'name': 'all'})
  970. flexmock(module).should_receive('restore_single_dump').with_args(
  971. repository=object,
  972. config=object,
  973. local_borg_version=object,
  974. global_arguments=object,
  975. local_path=object,
  976. remote_path=object,
  977. archive_name=object,
  978. hook_name='postgresql_databases',
  979. data_source={'name': 'foo', 'schemas': None},
  980. connection_params=object,
  981. borgmatic_runtime_directory=borgmatic_runtime_directory,
  982. ).once()
  983. flexmock(module).should_receive('ensure_requested_dumps_restored')
  984. module.run_restore(
  985. repository={'path': 'repo'},
  986. config=flexmock(),
  987. local_borg_version=flexmock(),
  988. restore_arguments=flexmock(
  989. repository='repo',
  990. archive='archive',
  991. data_sources=flexmock(),
  992. schemas=None,
  993. hostname=None,
  994. port=None,
  995. username=None,
  996. password=None,
  997. restore_path=None,
  998. ),
  999. global_arguments=flexmock(dry_run=False),
  1000. local_path=flexmock(),
  1001. remote_path=flexmock(),
  1002. )
  1003. def test_run_restore_restores_data_source_configured_with_all_name():
  1004. dumps_to_restore = {
  1005. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  1006. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  1007. }
  1008. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  1009. borgmatic_runtime_directory = flexmock()
  1010. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  1011. borgmatic_runtime_directory,
  1012. )
  1013. flexmock(module.borgmatic.config.paths).should_receive(
  1014. 'make_runtime_directory_glob',
  1015. ).replace_with(lambda path: path)
  1016. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  1017. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  1018. flexmock(),
  1019. )
  1020. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  1021. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  1022. flexmock(module).should_receive('get_configured_data_source').with_args(
  1023. config=object,
  1024. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  1025. ).and_return({'name': 'foo'})
  1026. flexmock(module).should_receive('get_configured_data_source').with_args(
  1027. config=object,
  1028. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  1029. ).and_return(None)
  1030. flexmock(module).should_receive('get_configured_data_source').with_args(
  1031. config=object,
  1032. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='all'),
  1033. ).and_return({'name': 'bar'})
  1034. flexmock(module).should_receive('restore_single_dump').with_args(
  1035. repository=object,
  1036. config=object,
  1037. local_borg_version=object,
  1038. global_arguments=object,
  1039. local_path=object,
  1040. remote_path=object,
  1041. archive_name=object,
  1042. hook_name='postgresql_databases',
  1043. data_source={'name': 'foo', 'schemas': None},
  1044. connection_params=object,
  1045. borgmatic_runtime_directory=borgmatic_runtime_directory,
  1046. ).once()
  1047. flexmock(module).should_receive('restore_single_dump').with_args(
  1048. repository=object,
  1049. config=object,
  1050. local_borg_version=object,
  1051. global_arguments=object,
  1052. local_path=object,
  1053. remote_path=object,
  1054. archive_name=object,
  1055. hook_name='postgresql_databases',
  1056. data_source={'name': 'bar', 'schemas': None},
  1057. connection_params=object,
  1058. borgmatic_runtime_directory=borgmatic_runtime_directory,
  1059. ).once()
  1060. flexmock(module).should_receive('ensure_requested_dumps_restored')
  1061. module.run_restore(
  1062. repository={'path': 'repo'},
  1063. config=flexmock(),
  1064. local_borg_version=flexmock(),
  1065. restore_arguments=flexmock(
  1066. repository='repo',
  1067. archive='archive',
  1068. data_sources=flexmock(),
  1069. schemas=None,
  1070. hostname=None,
  1071. port=None,
  1072. username=None,
  1073. password=None,
  1074. restore_path=None,
  1075. ),
  1076. global_arguments=flexmock(dry_run=False),
  1077. local_path=flexmock(),
  1078. remote_path=flexmock(),
  1079. )
  1080. def test_run_restore_skips_missing_data_source():
  1081. dumps_to_restore = {
  1082. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  1083. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  1084. }
  1085. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  1086. borgmatic_runtime_directory = flexmock()
  1087. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  1088. borgmatic_runtime_directory,
  1089. )
  1090. flexmock(module.borgmatic.config.paths).should_receive(
  1091. 'make_runtime_directory_glob',
  1092. ).replace_with(lambda path: path)
  1093. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  1094. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  1095. flexmock(),
  1096. )
  1097. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  1098. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  1099. flexmock(module).should_receive('get_configured_data_source').with_args(
  1100. config=object,
  1101. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  1102. ).and_return({'name': 'foo'})
  1103. flexmock(module).should_receive('get_configured_data_source').with_args(
  1104. config=object,
  1105. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  1106. ).and_return(None)
  1107. flexmock(module).should_receive('get_configured_data_source').with_args(
  1108. config=object,
  1109. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='all'),
  1110. ).and_return(None)
  1111. flexmock(module).should_receive('restore_single_dump').with_args(
  1112. repository=object,
  1113. config=object,
  1114. local_borg_version=object,
  1115. global_arguments=object,
  1116. local_path=object,
  1117. remote_path=object,
  1118. archive_name=object,
  1119. hook_name='postgresql_databases',
  1120. data_source={'name': 'foo', 'schemas': None},
  1121. connection_params=object,
  1122. borgmatic_runtime_directory=borgmatic_runtime_directory,
  1123. ).once()
  1124. flexmock(module).should_receive('restore_single_dump').with_args(
  1125. repository=object,
  1126. config=object,
  1127. local_borg_version=object,
  1128. global_arguments=object,
  1129. local_path=object,
  1130. remote_path=object,
  1131. archive_name=object,
  1132. hook_name='postgresql_databases',
  1133. data_source={'name': 'bar', 'schemas': None},
  1134. connection_params=object,
  1135. borgmatic_runtime_directory=borgmatic_runtime_directory,
  1136. ).never()
  1137. flexmock(module).should_receive('ensure_requested_dumps_restored')
  1138. module.run_restore(
  1139. repository={'path': 'repo'},
  1140. config=flexmock(),
  1141. local_borg_version=flexmock(),
  1142. restore_arguments=flexmock(
  1143. repository='repo',
  1144. archive='archive',
  1145. data_sources=flexmock(),
  1146. schemas=None,
  1147. hostname=None,
  1148. port=None,
  1149. username=None,
  1150. password=None,
  1151. restore_path=None,
  1152. ),
  1153. global_arguments=flexmock(dry_run=False),
  1154. local_path=flexmock(),
  1155. remote_path=flexmock(),
  1156. )
  1157. def test_run_restore_restores_data_sources_from_different_hooks():
  1158. dumps_to_restore = {
  1159. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  1160. module.Dump(hook_name='mysql_databases', data_source_name='foo'),
  1161. }
  1162. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  1163. borgmatic_runtime_directory = flexmock()
  1164. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  1165. borgmatic_runtime_directory,
  1166. )
  1167. flexmock(module.borgmatic.config.paths).should_receive(
  1168. 'make_runtime_directory_glob',
  1169. ).replace_with(lambda path: path)
  1170. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  1171. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  1172. flexmock(),
  1173. )
  1174. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  1175. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  1176. flexmock(module).should_receive('get_configured_data_source').with_args(
  1177. config=object,
  1178. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  1179. ).and_return({'name': 'foo'})
  1180. flexmock(module).should_receive('get_configured_data_source').with_args(
  1181. config=object,
  1182. restore_dump=module.Dump(hook_name='mysql_databases', data_source_name='foo'),
  1183. ).and_return({'name': 'bar'})
  1184. flexmock(module).should_receive('restore_single_dump').with_args(
  1185. repository=object,
  1186. config=object,
  1187. local_borg_version=object,
  1188. global_arguments=object,
  1189. local_path=object,
  1190. remote_path=object,
  1191. archive_name=object,
  1192. hook_name='postgresql_databases',
  1193. data_source={'name': 'foo', 'schemas': None},
  1194. connection_params=object,
  1195. borgmatic_runtime_directory=borgmatic_runtime_directory,
  1196. ).once()
  1197. flexmock(module).should_receive('restore_single_dump').with_args(
  1198. repository=object,
  1199. config=object,
  1200. local_borg_version=object,
  1201. global_arguments=object,
  1202. local_path=object,
  1203. remote_path=object,
  1204. archive_name=object,
  1205. hook_name='mysql_databases',
  1206. data_source={'name': 'bar', 'schemas': None},
  1207. connection_params=object,
  1208. borgmatic_runtime_directory=borgmatic_runtime_directory,
  1209. ).once()
  1210. flexmock(module).should_receive('ensure_requested_dumps_restored')
  1211. module.run_restore(
  1212. repository={'path': 'repo'},
  1213. config=flexmock(),
  1214. local_borg_version=flexmock(),
  1215. restore_arguments=flexmock(
  1216. repository='repo',
  1217. archive='archive',
  1218. data_sources=flexmock(),
  1219. schemas=None,
  1220. hostname=None,
  1221. port=None,
  1222. username=None,
  1223. password=None,
  1224. restore_path=None,
  1225. ),
  1226. global_arguments=flexmock(dry_run=False),
  1227. local_path=flexmock(),
  1228. remote_path=flexmock(),
  1229. )