test_restore.py 54 KB

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