test_restore.py 53 KB

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