test_restore.py 53 KB

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