2
0

test_postgresql.py 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570
  1. import logging
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.hooks.data_source import postgresql as module
  5. def test_make_environment_maps_options_to_environment():
  6. database = {
  7. 'name': 'foo',
  8. 'password': 'pass',
  9. 'ssl_mode': 'require',
  10. 'ssl_cert': 'cert.crt',
  11. 'ssl_key': 'key.key',
  12. 'ssl_root_cert': 'root.crt',
  13. 'ssl_crl': 'crl.crl',
  14. }
  15. expected = {
  16. 'USER': 'root',
  17. 'PGPASSWORD': 'pass',
  18. 'PGSSLMODE': 'require',
  19. 'PGSSLCERT': 'cert.crt',
  20. 'PGSSLKEY': 'key.key',
  21. 'PGSSLROOTCERT': 'root.crt',
  22. 'PGSSLCRL': 'crl.crl',
  23. }
  24. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  25. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  26. 'resolve_credential',
  27. ).replace_with(lambda value, config: value)
  28. assert module.make_environment(database, {}) == expected
  29. def test_make_environment_with_cli_password_sets_correct_password():
  30. database = {'name': 'foo', 'restore_password': 'trustsome1', 'password': 'anotherpassword'}
  31. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  32. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  33. 'resolve_credential',
  34. ).replace_with(lambda value, config: value)
  35. environment = module.make_environment(
  36. database,
  37. {},
  38. restore_connection_params={'password': 'clipassword'},
  39. )
  40. assert environment['PGPASSWORD'] == 'clipassword'
  41. def test_make_environment_without_cli_password_or_configured_password_does_not_set_password():
  42. database = {'name': 'foo'}
  43. environment = module.make_environment(
  44. database,
  45. {},
  46. restore_connection_params={'username': 'someone'},
  47. )
  48. assert 'PGPASSWORD' not in environment
  49. def test_make_environment_without_ssl_mode_does_not_set_ssl_mode():
  50. database = {'name': 'foo'}
  51. environment = module.make_environment(database, {})
  52. assert 'PGSSLMODE' not in environment
  53. def test_database_names_to_dump_passes_through_individual_database_name():
  54. database = {'name': 'foo'}
  55. assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
  56. def test_database_names_to_dump_passes_through_individual_database_name_with_format():
  57. database = {'name': 'foo', 'format': 'custom'}
  58. assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
  59. def test_database_names_to_dump_passes_through_all_without_format():
  60. database = {'name': 'all'}
  61. assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('all',)
  62. def test_database_names_to_dump_with_all_and_format_and_dry_run_bails():
  63. database = {'name': 'all', 'format': 'custom'}
  64. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  65. 'resolve_credential',
  66. ).replace_with(lambda value, config: value)
  67. flexmock(module).should_receive('execute_command_and_capture_output').never()
  68. assert module.database_names_to_dump(database, {}, flexmock(), dry_run=True) == ()
  69. def test_database_names_to_dump_with_all_and_format_lists_databases():
  70. database = {'name': 'all', 'format': 'custom'}
  71. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  72. 'resolve_credential',
  73. ).replace_with(lambda value, config: value)
  74. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  75. 'foo,test,\nbar,test,"stuff and such"',
  76. )
  77. assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
  78. 'foo',
  79. 'bar',
  80. )
  81. def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostname_and_port():
  82. database = {'name': 'all', 'format': 'custom', 'hostname': 'localhost', 'port': 1234}
  83. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  84. 'resolve_credential',
  85. ).replace_with(lambda value, config: value)
  86. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  87. (
  88. 'psql',
  89. '--list',
  90. '--no-password',
  91. '--no-psqlrc',
  92. '--csv',
  93. '--tuples-only',
  94. '--host',
  95. 'localhost',
  96. '--port',
  97. '1234',
  98. ),
  99. environment=object,
  100. ).and_return('foo,test,\nbar,test,"stuff and such"')
  101. assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
  102. 'foo',
  103. 'bar',
  104. )
  105. def test_database_names_to_dump_with_all_and_format_lists_databases_with_username():
  106. database = {'name': 'all', 'format': 'custom', 'username': 'postgres'}
  107. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  108. 'resolve_credential',
  109. ).replace_with(lambda value, config: value)
  110. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  111. (
  112. 'psql',
  113. '--list',
  114. '--no-password',
  115. '--no-psqlrc',
  116. '--csv',
  117. '--tuples-only',
  118. '--username',
  119. 'postgres',
  120. ),
  121. environment=object,
  122. ).and_return('foo,test,\nbar,test,"stuff and such"')
  123. assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
  124. 'foo',
  125. 'bar',
  126. )
  127. def test_database_names_to_dump_with_all_and_format_lists_databases_with_options():
  128. database = {'name': 'all', 'format': 'custom', 'list_options': '--harder'}
  129. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  130. 'resolve_credential',
  131. ).replace_with(lambda value, config: value)
  132. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  133. ('psql', '--list', '--no-password', '--no-psqlrc', '--csv', '--tuples-only', '--harder'),
  134. environment=object,
  135. ).and_return('foo,test,\nbar,test,"stuff and such"')
  136. assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
  137. 'foo',
  138. 'bar',
  139. )
  140. def test_database_names_to_dump_with_all_and_format_excludes_particular_databases():
  141. database = {'name': 'all', 'format': 'custom'}
  142. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  143. 'resolve_credential',
  144. ).replace_with(lambda value, config: value)
  145. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  146. 'foo,test,\ntemplate0,test,blah',
  147. )
  148. assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
  149. def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
  150. database = {
  151. 'name': 'all',
  152. 'format': 'custom',
  153. 'psql_command': 'docker exec --workdir * mycontainer psql',
  154. }
  155. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  156. 'resolve_credential',
  157. ).replace_with(lambda value, config: value)
  158. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  159. (
  160. 'docker',
  161. 'exec',
  162. '--workdir',
  163. "'*'", # Should get shell escaped to prevent injection attacks.
  164. 'mycontainer',
  165. 'psql',
  166. '--list',
  167. '--no-password',
  168. '--no-psqlrc',
  169. '--csv',
  170. '--tuples-only',
  171. ),
  172. environment=object,
  173. ).and_return('foo,text').once()
  174. assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
  175. def test_use_streaming_true_for_any_non_directory_format_databases():
  176. assert module.use_streaming(
  177. databases=[{'format': 'stuff'}, {'format': 'directory'}, {}],
  178. config=flexmock(),
  179. )
  180. def test_use_streaming_false_for_all_directory_format_databases():
  181. assert not module.use_streaming(
  182. databases=[{'format': 'directory'}, {'format': 'directory'}],
  183. config=flexmock(),
  184. )
  185. def test_use_streaming_false_for_no_databases():
  186. assert not module.use_streaming(databases=[], config=flexmock())
  187. def test_dump_data_sources_runs_pg_dump_for_each_database():
  188. databases = [{'name': 'foo'}, {'name': 'bar'}]
  189. processes = [flexmock(), flexmock()]
  190. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  191. flexmock(module).should_receive('make_dump_path').and_return('')
  192. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  193. ('bar',),
  194. )
  195. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  196. 'databases/localhost/foo',
  197. ).and_return('databases/localhost/bar')
  198. flexmock(module.os.path).should_receive('exists').and_return(False)
  199. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  200. 'resolve_credential',
  201. ).replace_with(lambda value, config: value)
  202. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  203. for name, process in zip(('foo', 'bar'), processes):
  204. flexmock(module).should_receive('execute_command').with_args(
  205. (
  206. 'pg_dump',
  207. '--no-password',
  208. '--clean',
  209. '--if-exists',
  210. '--format',
  211. 'custom',
  212. name,
  213. '>',
  214. f'databases/localhost/{name}',
  215. ),
  216. shell=True,
  217. environment={'PGSSLMODE': 'disable'},
  218. run_to_completion=False,
  219. ).and_return(process).once()
  220. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  221. '/run/borgmatic',
  222. 'postgresql_databases',
  223. [
  224. module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
  225. module.borgmatic.actions.restore.Dump('postgresql_databases', 'bar'),
  226. ],
  227. ).once()
  228. assert (
  229. module.dump_data_sources(
  230. databases,
  231. {},
  232. config_paths=('test.yaml',),
  233. borgmatic_runtime_directory='/run/borgmatic',
  234. patterns=[],
  235. dry_run=False,
  236. )
  237. == processes
  238. )
  239. def test_dump_data_sources_raises_when_no_database_names_to_dump():
  240. databases = [{'name': 'foo'}, {'name': 'bar'}]
  241. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  242. flexmock(module).should_receive('make_dump_path').and_return('')
  243. flexmock(module).should_receive('database_names_to_dump').and_return(())
  244. with pytest.raises(ValueError):
  245. module.dump_data_sources(
  246. databases,
  247. {},
  248. config_paths=('test.yaml',),
  249. borgmatic_runtime_directory='/run/borgmatic',
  250. patterns=[],
  251. dry_run=False,
  252. )
  253. def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
  254. databases = [{'name': 'foo'}, {'name': 'bar'}]
  255. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  256. flexmock(module).should_receive('make_dump_path').and_return('')
  257. flexmock(module).should_receive('database_names_to_dump').and_return(())
  258. assert (
  259. module.dump_data_sources(
  260. databases,
  261. {},
  262. config_paths=('test.yaml',),
  263. borgmatic_runtime_directory='/run/borgmatic',
  264. patterns=[],
  265. dry_run=True,
  266. )
  267. == []
  268. )
  269. def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
  270. databases = [{'name': 'foo'}, {'name': 'bar'}]
  271. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  272. flexmock(module).should_receive('make_dump_path').and_return('')
  273. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  274. ('bar',),
  275. )
  276. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  277. 'databases/localhost/foo',
  278. ).and_return('databases/localhost/bar')
  279. flexmock(module.os.path).should_receive('exists').and_return(True)
  280. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  281. flexmock(module).should_receive('execute_command').never()
  282. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  283. '/run/borgmatic',
  284. 'postgresql_databases',
  285. [
  286. module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
  287. module.borgmatic.actions.restore.Dump('postgresql_databases', 'bar'),
  288. ],
  289. ).once()
  290. assert (
  291. module.dump_data_sources(
  292. databases,
  293. {},
  294. config_paths=('test.yaml',),
  295. borgmatic_runtime_directory='/run/borgmatic',
  296. patterns=[],
  297. dry_run=False,
  298. )
  299. == []
  300. )
  301. def test_dump_data_sources_with_dry_run_skips_pg_dump():
  302. databases = [{'name': 'foo'}, {'name': 'bar'}]
  303. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  304. flexmock(module).should_receive('make_dump_path').and_return('')
  305. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  306. ('bar',),
  307. )
  308. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  309. 'databases/localhost/foo',
  310. ).and_return('databases/localhost/bar')
  311. flexmock(module.os.path).should_receive('exists').and_return(False)
  312. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  313. 'resolve_credential',
  314. ).replace_with(lambda value, config: value)
  315. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  316. flexmock(module).should_receive('execute_command').never()
  317. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').never()
  318. assert (
  319. module.dump_data_sources(
  320. databases,
  321. {},
  322. config_paths=('test.yaml',),
  323. borgmatic_runtime_directory='/run/borgmatic',
  324. patterns=[],
  325. dry_run=True,
  326. )
  327. == []
  328. )
  329. def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
  330. databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
  331. process = flexmock()
  332. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  333. flexmock(module).should_receive('make_dump_path').and_return('')
  334. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  335. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  336. 'databases/database.example.org/foo',
  337. )
  338. flexmock(module.os.path).should_receive('exists').and_return(False)
  339. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  340. 'resolve_credential',
  341. ).replace_with(lambda value, config: value)
  342. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  343. flexmock(module).should_receive('execute_command').with_args(
  344. (
  345. 'pg_dump',
  346. '--no-password',
  347. '--clean',
  348. '--if-exists',
  349. '--host',
  350. 'database.example.org',
  351. '--port',
  352. '5433',
  353. '--format',
  354. 'custom',
  355. 'foo',
  356. '>',
  357. 'databases/database.example.org/foo',
  358. ),
  359. shell=True,
  360. environment={'PGSSLMODE': 'disable'},
  361. run_to_completion=False,
  362. ).and_return(process).once()
  363. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  364. '/run/borgmatic',
  365. 'postgresql_databases',
  366. [
  367. module.borgmatic.actions.restore.Dump(
  368. 'postgresql_databases', 'foo', 'database.example.org', 5433
  369. ),
  370. ],
  371. ).once()
  372. assert module.dump_data_sources(
  373. databases,
  374. {},
  375. config_paths=('test.yaml',),
  376. borgmatic_runtime_directory='/run/borgmatic',
  377. patterns=[],
  378. dry_run=False,
  379. ) == [process]
  380. def test_dump_data_sources_runs_pg_dump_with_username_and_password():
  381. databases = [{'name': 'foo', 'username': 'postgres', 'password': 'trustsome1'}]
  382. process = flexmock()
  383. flexmock(module).should_receive('make_environment').and_return(
  384. {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  385. )
  386. flexmock(module).should_receive('make_dump_path').and_return('')
  387. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  388. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  389. 'databases/localhost/foo',
  390. )
  391. flexmock(module.os.path).should_receive('exists').and_return(False)
  392. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  393. 'resolve_credential',
  394. ).replace_with(lambda value, config: value)
  395. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  396. flexmock(module).should_receive('execute_command').with_args(
  397. (
  398. 'pg_dump',
  399. '--no-password',
  400. '--clean',
  401. '--if-exists',
  402. '--username',
  403. 'postgres',
  404. '--format',
  405. 'custom',
  406. 'foo',
  407. '>',
  408. 'databases/localhost/foo',
  409. ),
  410. shell=True,
  411. environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  412. run_to_completion=False,
  413. ).and_return(process).once()
  414. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  415. '/run/borgmatic',
  416. 'postgresql_databases',
  417. [
  418. module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
  419. ],
  420. ).once()
  421. assert module.dump_data_sources(
  422. databases,
  423. {},
  424. config_paths=('test.yaml',),
  425. borgmatic_runtime_directory='/run/borgmatic',
  426. patterns=[],
  427. dry_run=False,
  428. ) == [process]
  429. def test_dump_data_sources_with_username_injection_attack_gets_escaped():
  430. databases = [{'name': 'foo', 'username': 'postgres; naughty-command', 'password': 'trustsome1'}]
  431. process = flexmock()
  432. flexmock(module).should_receive('make_environment').and_return(
  433. {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  434. )
  435. flexmock(module).should_receive('make_dump_path').and_return('')
  436. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  437. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  438. 'databases/localhost/foo',
  439. )
  440. flexmock(module.os.path).should_receive('exists').and_return(False)
  441. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  442. 'resolve_credential',
  443. ).replace_with(lambda value, config: value)
  444. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  445. flexmock(module).should_receive('execute_command').with_args(
  446. (
  447. 'pg_dump',
  448. '--no-password',
  449. '--clean',
  450. '--if-exists',
  451. '--username',
  452. "'postgres; naughty-command'",
  453. '--format',
  454. 'custom',
  455. 'foo',
  456. '>',
  457. 'databases/localhost/foo',
  458. ),
  459. shell=True,
  460. environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  461. run_to_completion=False,
  462. ).and_return(process).once()
  463. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  464. '/run/borgmatic',
  465. 'postgresql_databases',
  466. [
  467. module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
  468. ],
  469. ).once()
  470. assert module.dump_data_sources(
  471. databases,
  472. {},
  473. config_paths=('test.yaml',),
  474. borgmatic_runtime_directory='/run/borgmatic',
  475. patterns=[],
  476. dry_run=False,
  477. ) == [process]
  478. def test_dump_data_sources_runs_pg_dump_with_directory_format():
  479. databases = [{'name': 'foo', 'format': 'directory'}]
  480. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  481. flexmock(module).should_receive('make_dump_path').and_return('')
  482. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  483. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  484. 'databases/localhost/foo',
  485. )
  486. flexmock(module.os.path).should_receive('exists').and_return(False)
  487. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  488. 'resolve_credential',
  489. ).replace_with(lambda value, config: value)
  490. flexmock(module.dump).should_receive('create_parent_directory_for_dump')
  491. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  492. flexmock(module).should_receive('execute_command').with_args(
  493. (
  494. 'pg_dump',
  495. '--no-password',
  496. '--clean',
  497. '--if-exists',
  498. '--format',
  499. 'directory',
  500. '--file',
  501. 'databases/localhost/foo',
  502. 'foo',
  503. ),
  504. shell=True,
  505. environment={'PGSSLMODE': 'disable'},
  506. ).and_return(flexmock()).once()
  507. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  508. '/run/borgmatic',
  509. 'postgresql_databases',
  510. [
  511. module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
  512. ],
  513. ).once()
  514. assert (
  515. module.dump_data_sources(
  516. databases,
  517. {},
  518. config_paths=('test.yaml',),
  519. borgmatic_runtime_directory='/run/borgmatic',
  520. patterns=[],
  521. dry_run=False,
  522. )
  523. == []
  524. )
  525. def test_dump_data_sources_runs_pg_dump_with_string_compression():
  526. databases = [{'name': 'foo', 'compression': 'winrar'}]
  527. processes = [flexmock()]
  528. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  529. flexmock(module).should_receive('make_dump_path').and_return('')
  530. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  531. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  532. 'databases/localhost/foo',
  533. )
  534. flexmock(module.os.path).should_receive('exists').and_return(False)
  535. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  536. 'resolve_credential',
  537. ).replace_with(lambda value, config: value)
  538. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  539. flexmock(module).should_receive('execute_command').with_args(
  540. (
  541. 'pg_dump',
  542. '--no-password',
  543. '--clean',
  544. '--if-exists',
  545. '--format',
  546. 'custom',
  547. '--compress',
  548. 'winrar',
  549. 'foo',
  550. '>',
  551. 'databases/localhost/foo',
  552. ),
  553. shell=True,
  554. environment={'PGSSLMODE': 'disable'},
  555. run_to_completion=False,
  556. ).and_return(processes[0]).once()
  557. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  558. '/run/borgmatic',
  559. 'postgresql_databases',
  560. [
  561. module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
  562. ],
  563. ).once()
  564. assert (
  565. module.dump_data_sources(
  566. databases,
  567. {},
  568. config_paths=('test.yaml',),
  569. borgmatic_runtime_directory='/run/borgmatic',
  570. patterns=[],
  571. dry_run=False,
  572. )
  573. == processes
  574. )
  575. def test_dump_data_sources_runs_pg_dump_with_integer_compression():
  576. databases = [{'name': 'foo', 'compression': 0}]
  577. processes = [flexmock()]
  578. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  579. flexmock(module).should_receive('make_dump_path').and_return('')
  580. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  581. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  582. 'databases/localhost/foo',
  583. )
  584. flexmock(module.os.path).should_receive('exists').and_return(False)
  585. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  586. 'resolve_credential',
  587. ).replace_with(lambda value, config: value)
  588. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  589. flexmock(module).should_receive('execute_command').with_args(
  590. (
  591. 'pg_dump',
  592. '--no-password',
  593. '--clean',
  594. '--if-exists',
  595. '--format',
  596. 'custom',
  597. '--compress',
  598. '0',
  599. 'foo',
  600. '>',
  601. 'databases/localhost/foo',
  602. ),
  603. shell=True,
  604. environment={'PGSSLMODE': 'disable'},
  605. run_to_completion=False,
  606. ).and_return(processes[0]).once()
  607. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  608. '/run/borgmatic',
  609. 'postgresql_databases',
  610. [
  611. module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
  612. ],
  613. ).once()
  614. assert (
  615. module.dump_data_sources(
  616. databases,
  617. {},
  618. config_paths=('test.yaml',),
  619. borgmatic_runtime_directory='/run/borgmatic',
  620. patterns=[],
  621. dry_run=False,
  622. )
  623. == processes
  624. )
  625. def test_dump_data_sources_runs_pg_dump_with_options():
  626. databases = [{'name': 'foo', 'options': '--stuff=such'}]
  627. process = flexmock()
  628. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  629. flexmock(module).should_receive('make_dump_path').and_return('')
  630. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  631. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  632. 'databases/localhost/foo',
  633. )
  634. flexmock(module.os.path).should_receive('exists').and_return(False)
  635. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  636. 'resolve_credential',
  637. ).replace_with(lambda value, config: value)
  638. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  639. flexmock(module).should_receive('execute_command').with_args(
  640. (
  641. 'pg_dump',
  642. '--no-password',
  643. '--clean',
  644. '--if-exists',
  645. '--format',
  646. 'custom',
  647. '--stuff=such',
  648. 'foo',
  649. '>',
  650. 'databases/localhost/foo',
  651. ),
  652. shell=True,
  653. environment={'PGSSLMODE': 'disable'},
  654. run_to_completion=False,
  655. ).and_return(process).once()
  656. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  657. '/run/borgmatic',
  658. 'postgresql_databases',
  659. [
  660. module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
  661. ],
  662. ).once()
  663. assert module.dump_data_sources(
  664. databases,
  665. {},
  666. config_paths=('test.yaml',),
  667. borgmatic_runtime_directory='/run/borgmatic',
  668. patterns=[],
  669. dry_run=False,
  670. ) == [process]
  671. def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
  672. databases = [{'name': 'all'}]
  673. process = flexmock()
  674. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  675. flexmock(module).should_receive('make_dump_path').and_return('')
  676. flexmock(module).should_receive('database_names_to_dump').and_return(('all',))
  677. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  678. 'databases/localhost/all',
  679. )
  680. flexmock(module.os.path).should_receive('exists').and_return(False)
  681. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  682. 'resolve_credential',
  683. ).replace_with(lambda value, config: value)
  684. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  685. flexmock(module).should_receive('execute_command').with_args(
  686. ('pg_dumpall', '--no-password', '--clean', '--if-exists', '>', 'databases/localhost/all'),
  687. shell=True,
  688. environment={'PGSSLMODE': 'disable'},
  689. run_to_completion=False,
  690. ).and_return(process).once()
  691. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  692. '/run/borgmatic',
  693. 'postgresql_databases',
  694. [
  695. module.borgmatic.actions.restore.Dump('postgresql_databases', 'all'),
  696. ],
  697. ).once()
  698. assert module.dump_data_sources(
  699. databases,
  700. {},
  701. config_paths=('test.yaml',),
  702. borgmatic_runtime_directory='/run/borgmatic',
  703. patterns=[],
  704. dry_run=False,
  705. ) == [process]
  706. def test_dump_data_sources_runs_non_default_pg_dump():
  707. databases = [{'name': 'foo', 'pg_dump_command': 'special_pg_dump --compress *'}]
  708. process = flexmock()
  709. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  710. flexmock(module).should_receive('make_dump_path').and_return('')
  711. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  712. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  713. 'databases/localhost/foo',
  714. )
  715. flexmock(module.os.path).should_receive('exists').and_return(False)
  716. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  717. 'resolve_credential',
  718. ).replace_with(lambda value, config: value)
  719. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  720. flexmock(module).should_receive('execute_command').with_args(
  721. (
  722. 'special_pg_dump',
  723. '--compress',
  724. "'*'", # Should get shell escaped to prevent injection attacks.
  725. '--no-password',
  726. '--clean',
  727. '--if-exists',
  728. '--format',
  729. 'custom',
  730. 'foo',
  731. '>',
  732. 'databases/localhost/foo',
  733. ),
  734. shell=True,
  735. environment={'PGSSLMODE': 'disable'},
  736. run_to_completion=False,
  737. ).and_return(process).once()
  738. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  739. '/run/borgmatic',
  740. 'postgresql_databases',
  741. [
  742. module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
  743. ],
  744. ).once()
  745. assert module.dump_data_sources(
  746. databases,
  747. {},
  748. config_paths=('test.yaml',),
  749. borgmatic_runtime_directory='/run/borgmatic',
  750. patterns=[],
  751. dry_run=False,
  752. ) == [process]
  753. def test_restore_data_source_dump_runs_pg_restore():
  754. hook_config = [{'name': 'foo', 'schemas': None}, {'name': 'bar'}]
  755. extract_process = flexmock(stdout=flexmock())
  756. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  757. 'resolve_credential',
  758. ).replace_with(lambda value, config: value)
  759. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  760. flexmock(module).should_receive('make_dump_path')
  761. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  762. flexmock(module).should_receive('execute_command_with_processes').with_args(
  763. (
  764. 'pg_restore',
  765. '--no-password',
  766. '--if-exists',
  767. '--exit-on-error',
  768. '--clean',
  769. '--dbname',
  770. 'foo',
  771. ),
  772. processes=[extract_process],
  773. output_log_level=logging.DEBUG,
  774. input_file=extract_process.stdout,
  775. environment={'PGSSLMODE': 'disable'},
  776. ).once()
  777. flexmock(module).should_receive('execute_command').with_args(
  778. (
  779. 'psql',
  780. '--no-password',
  781. '--no-psqlrc',
  782. '--quiet',
  783. '--dbname',
  784. 'foo',
  785. '--command',
  786. 'ANALYZE',
  787. ),
  788. environment={'PGSSLMODE': 'disable'},
  789. ).once()
  790. module.restore_data_source_dump(
  791. hook_config,
  792. {},
  793. data_source={'name': 'foo'},
  794. dry_run=False,
  795. extract_process=extract_process,
  796. connection_params={
  797. 'hostname': None,
  798. 'port': None,
  799. 'username': None,
  800. 'password': None,
  801. },
  802. borgmatic_runtime_directory='/run/borgmatic',
  803. )
  804. def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
  805. hook_config = [
  806. {'name': 'foo', 'hostname': 'database.example.org', 'port': 5433, 'schemas': None},
  807. ]
  808. extract_process = flexmock(stdout=flexmock())
  809. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  810. 'resolve_credential',
  811. ).replace_with(lambda value, config: value)
  812. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  813. flexmock(module).should_receive('make_dump_path')
  814. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  815. flexmock(module).should_receive('execute_command_with_processes').with_args(
  816. (
  817. 'pg_restore',
  818. '--no-password',
  819. '--if-exists',
  820. '--exit-on-error',
  821. '--clean',
  822. '--dbname',
  823. 'foo',
  824. '--host',
  825. 'database.example.org',
  826. '--port',
  827. '5433',
  828. ),
  829. processes=[extract_process],
  830. output_log_level=logging.DEBUG,
  831. input_file=extract_process.stdout,
  832. environment={'PGSSLMODE': 'disable'},
  833. ).once()
  834. flexmock(module).should_receive('execute_command').with_args(
  835. (
  836. 'psql',
  837. '--no-password',
  838. '--no-psqlrc',
  839. '--quiet',
  840. '--host',
  841. 'database.example.org',
  842. '--port',
  843. '5433',
  844. '--dbname',
  845. 'foo',
  846. '--command',
  847. 'ANALYZE',
  848. ),
  849. environment={'PGSSLMODE': 'disable'},
  850. ).once()
  851. module.restore_data_source_dump(
  852. hook_config,
  853. {},
  854. data_source=hook_config[0],
  855. dry_run=False,
  856. extract_process=extract_process,
  857. connection_params={
  858. 'hostname': None,
  859. 'port': None,
  860. 'username': None,
  861. 'password': None,
  862. },
  863. borgmatic_runtime_directory='/run/borgmatic',
  864. )
  865. def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
  866. hook_config = [
  867. {'name': 'foo', 'username': 'postgres', 'password': 'trustsome1', 'schemas': None},
  868. ]
  869. extract_process = flexmock(stdout=flexmock())
  870. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  871. 'resolve_credential',
  872. ).replace_with(lambda value, config: value)
  873. flexmock(module).should_receive('make_environment').and_return(
  874. {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  875. )
  876. flexmock(module).should_receive('make_dump_path')
  877. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  878. flexmock(module).should_receive('execute_command_with_processes').with_args(
  879. (
  880. 'pg_restore',
  881. '--no-password',
  882. '--if-exists',
  883. '--exit-on-error',
  884. '--clean',
  885. '--dbname',
  886. 'foo',
  887. '--username',
  888. 'postgres',
  889. ),
  890. processes=[extract_process],
  891. output_log_level=logging.DEBUG,
  892. input_file=extract_process.stdout,
  893. environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  894. ).once()
  895. flexmock(module).should_receive('execute_command').with_args(
  896. (
  897. 'psql',
  898. '--no-password',
  899. '--no-psqlrc',
  900. '--quiet',
  901. '--username',
  902. 'postgres',
  903. '--dbname',
  904. 'foo',
  905. '--command',
  906. 'ANALYZE',
  907. ),
  908. environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  909. ).once()
  910. module.restore_data_source_dump(
  911. hook_config,
  912. {},
  913. data_source=hook_config[0],
  914. dry_run=False,
  915. extract_process=extract_process,
  916. connection_params={
  917. 'hostname': None,
  918. 'port': None,
  919. 'username': None,
  920. 'password': None,
  921. },
  922. borgmatic_runtime_directory='/run/borgmatic',
  923. )
  924. def test_restore_data_source_dump_with_connection_params_uses_connection_params_for_restore():
  925. hook_config = [
  926. {
  927. 'name': 'foo',
  928. 'hostname': 'database.example.org',
  929. 'port': 5433,
  930. 'username': 'postgres',
  931. 'password': 'trustsome1',
  932. 'restore_hostname': 'restorehost',
  933. 'restore_port': 'restoreport',
  934. 'restore_username': 'restoreusername',
  935. 'restore_password': 'restorepassword',
  936. 'schemas': None,
  937. },
  938. ]
  939. extract_process = flexmock(stdout=flexmock())
  940. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  941. 'resolve_credential',
  942. ).replace_with(lambda value, config: value)
  943. flexmock(module).should_receive('make_environment').and_return(
  944. {'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
  945. )
  946. flexmock(module).should_receive('make_dump_path')
  947. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  948. flexmock(module).should_receive('execute_command_with_processes').with_args(
  949. (
  950. 'pg_restore',
  951. '--no-password',
  952. '--if-exists',
  953. '--exit-on-error',
  954. '--clean',
  955. '--dbname',
  956. 'foo',
  957. '--host',
  958. 'clihost',
  959. '--port',
  960. 'cliport',
  961. '--username',
  962. 'cliusername',
  963. ),
  964. processes=[extract_process],
  965. output_log_level=logging.DEBUG,
  966. input_file=extract_process.stdout,
  967. environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
  968. ).once()
  969. flexmock(module).should_receive('execute_command').with_args(
  970. (
  971. 'psql',
  972. '--no-password',
  973. '--no-psqlrc',
  974. '--quiet',
  975. '--host',
  976. 'clihost',
  977. '--port',
  978. 'cliport',
  979. '--username',
  980. 'cliusername',
  981. '--dbname',
  982. 'foo',
  983. '--command',
  984. 'ANALYZE',
  985. ),
  986. environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
  987. ).once()
  988. module.restore_data_source_dump(
  989. hook_config,
  990. {},
  991. data_source={'name': 'foo'},
  992. dry_run=False,
  993. extract_process=extract_process,
  994. connection_params={
  995. 'hostname': 'clihost',
  996. 'port': 'cliport',
  997. 'username': 'cliusername',
  998. 'password': 'clipassword',
  999. },
  1000. borgmatic_runtime_directory='/run/borgmatic',
  1001. )
  1002. def test_restore_data_source_dump_without_connection_params_uses_restore_params_in_config_for_restore():
  1003. hook_config = [
  1004. {
  1005. 'name': 'foo',
  1006. 'hostname': 'database.example.org',
  1007. 'port': 5433,
  1008. 'username': 'postgres',
  1009. 'password': 'trustsome1',
  1010. 'schemas': None,
  1011. 'restore_hostname': 'restorehost',
  1012. 'restore_port': 'restoreport',
  1013. 'restore_username': 'restoreusername',
  1014. 'restore_password': 'restorepassword',
  1015. },
  1016. ]
  1017. extract_process = flexmock(stdout=flexmock())
  1018. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1019. 'resolve_credential',
  1020. ).replace_with(lambda value, config: value)
  1021. flexmock(module).should_receive('make_environment').and_return(
  1022. {'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
  1023. )
  1024. flexmock(module).should_receive('make_dump_path')
  1025. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  1026. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1027. (
  1028. 'pg_restore',
  1029. '--no-password',
  1030. '--if-exists',
  1031. '--exit-on-error',
  1032. '--clean',
  1033. '--dbname',
  1034. 'foo',
  1035. '--host',
  1036. 'restorehost',
  1037. '--port',
  1038. 'restoreport',
  1039. '--username',
  1040. 'restoreusername',
  1041. ),
  1042. processes=[extract_process],
  1043. output_log_level=logging.DEBUG,
  1044. input_file=extract_process.stdout,
  1045. environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
  1046. ).once()
  1047. flexmock(module).should_receive('execute_command').with_args(
  1048. (
  1049. 'psql',
  1050. '--no-password',
  1051. '--no-psqlrc',
  1052. '--quiet',
  1053. '--host',
  1054. 'restorehost',
  1055. '--port',
  1056. 'restoreport',
  1057. '--username',
  1058. 'restoreusername',
  1059. '--dbname',
  1060. 'foo',
  1061. '--command',
  1062. 'ANALYZE',
  1063. ),
  1064. environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
  1065. ).once()
  1066. module.restore_data_source_dump(
  1067. hook_config,
  1068. {},
  1069. data_source=hook_config[0],
  1070. dry_run=False,
  1071. extract_process=extract_process,
  1072. connection_params={
  1073. 'hostname': None,
  1074. 'port': None,
  1075. 'username': None,
  1076. 'password': None,
  1077. },
  1078. borgmatic_runtime_directory='/run/borgmatic',
  1079. )
  1080. def test_restore_data_source_dump_runs_pg_restore_with_options():
  1081. hook_config = [
  1082. {
  1083. 'name': 'foo',
  1084. 'restore_options': '--harder',
  1085. 'analyze_options': '--smarter',
  1086. 'schemas': None,
  1087. },
  1088. ]
  1089. extract_process = flexmock(stdout=flexmock())
  1090. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1091. 'resolve_credential',
  1092. ).replace_with(lambda value, config: value)
  1093. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1094. flexmock(module).should_receive('make_dump_path')
  1095. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  1096. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1097. (
  1098. 'pg_restore',
  1099. '--no-password',
  1100. '--if-exists',
  1101. '--exit-on-error',
  1102. '--clean',
  1103. '--dbname',
  1104. 'foo',
  1105. '--harder',
  1106. ),
  1107. processes=[extract_process],
  1108. output_log_level=logging.DEBUG,
  1109. input_file=extract_process.stdout,
  1110. environment={'PGSSLMODE': 'disable'},
  1111. ).once()
  1112. flexmock(module).should_receive('execute_command').with_args(
  1113. (
  1114. 'psql',
  1115. '--no-password',
  1116. '--no-psqlrc',
  1117. '--quiet',
  1118. '--dbname',
  1119. 'foo',
  1120. '--smarter',
  1121. '--command',
  1122. 'ANALYZE',
  1123. ),
  1124. environment={'PGSSLMODE': 'disable'},
  1125. ).once()
  1126. module.restore_data_source_dump(
  1127. hook_config,
  1128. {},
  1129. data_source=hook_config[0],
  1130. dry_run=False,
  1131. extract_process=extract_process,
  1132. connection_params={
  1133. 'hostname': None,
  1134. 'port': None,
  1135. 'username': None,
  1136. 'password': None,
  1137. },
  1138. borgmatic_runtime_directory='/run/borgmatic',
  1139. )
  1140. def test_restore_data_source_dump_runs_psql_for_all_database_dump():
  1141. hook_config = [{'name': 'all', 'schemas': None}]
  1142. extract_process = flexmock(stdout=flexmock())
  1143. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1144. 'resolve_credential',
  1145. ).replace_with(lambda value, config: value)
  1146. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1147. flexmock(module).should_receive('make_dump_path')
  1148. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  1149. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1150. (
  1151. 'psql',
  1152. '--no-password',
  1153. '--no-psqlrc',
  1154. ),
  1155. processes=[extract_process],
  1156. output_log_level=logging.DEBUG,
  1157. input_file=extract_process.stdout,
  1158. environment={'PGSSLMODE': 'disable'},
  1159. ).once()
  1160. flexmock(module).should_receive('execute_command').with_args(
  1161. ('psql', '--no-password', '--no-psqlrc', '--quiet', '--command', 'ANALYZE'),
  1162. environment={'PGSSLMODE': 'disable'},
  1163. ).once()
  1164. module.restore_data_source_dump(
  1165. hook_config,
  1166. {},
  1167. data_source={'name': 'all'},
  1168. dry_run=False,
  1169. extract_process=extract_process,
  1170. connection_params={
  1171. 'hostname': None,
  1172. 'port': None,
  1173. 'username': None,
  1174. 'password': None,
  1175. },
  1176. borgmatic_runtime_directory='/run/borgmatic',
  1177. )
  1178. def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
  1179. hook_config = [{'name': 'foo', 'format': 'plain', 'schemas': None}]
  1180. extract_process = flexmock(stdout=flexmock())
  1181. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1182. 'resolve_credential',
  1183. ).replace_with(lambda value, config: value)
  1184. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1185. flexmock(module).should_receive('make_dump_path')
  1186. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  1187. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1188. ('psql', '--no-password', '--no-psqlrc', '--dbname', 'foo'),
  1189. processes=[extract_process],
  1190. output_log_level=logging.DEBUG,
  1191. input_file=extract_process.stdout,
  1192. environment={'PGSSLMODE': 'disable'},
  1193. ).once()
  1194. flexmock(module).should_receive('execute_command').with_args(
  1195. (
  1196. 'psql',
  1197. '--no-password',
  1198. '--no-psqlrc',
  1199. '--quiet',
  1200. '--dbname',
  1201. 'foo',
  1202. '--command',
  1203. 'ANALYZE',
  1204. ),
  1205. environment={'PGSSLMODE': 'disable'},
  1206. ).once()
  1207. module.restore_data_source_dump(
  1208. hook_config,
  1209. {},
  1210. data_source=hook_config[0],
  1211. dry_run=False,
  1212. extract_process=extract_process,
  1213. connection_params={
  1214. 'hostname': None,
  1215. 'port': None,
  1216. 'username': None,
  1217. 'password': None,
  1218. },
  1219. borgmatic_runtime_directory='/run/borgmatic',
  1220. )
  1221. def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
  1222. hook_config = [
  1223. {
  1224. 'name': 'foo',
  1225. 'pg_restore_command': 'docker exec --workdir * mycontainer pg_restore',
  1226. 'psql_command': 'docker exec --workdir * mycontainer psql',
  1227. 'schemas': None,
  1228. },
  1229. ]
  1230. extract_process = flexmock(stdout=flexmock())
  1231. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1232. 'resolve_credential',
  1233. ).replace_with(lambda value, config: value)
  1234. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1235. flexmock(module).should_receive('make_dump_path')
  1236. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  1237. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1238. (
  1239. 'docker',
  1240. 'exec',
  1241. '--workdir',
  1242. "'*'", # Should get shell escaped to prevent injection attacks.
  1243. 'mycontainer',
  1244. 'pg_restore',
  1245. '--no-password',
  1246. '--if-exists',
  1247. '--exit-on-error',
  1248. '--clean',
  1249. '--dbname',
  1250. 'foo',
  1251. ),
  1252. processes=[extract_process],
  1253. output_log_level=logging.DEBUG,
  1254. input_file=extract_process.stdout,
  1255. environment={'PGSSLMODE': 'disable'},
  1256. ).once()
  1257. flexmock(module).should_receive('execute_command').with_args(
  1258. (
  1259. 'docker',
  1260. 'exec',
  1261. '--workdir',
  1262. "'*'", # Should get shell escaped to prevent injection attacks.
  1263. 'mycontainer',
  1264. 'psql',
  1265. '--no-password',
  1266. '--no-psqlrc',
  1267. '--quiet',
  1268. '--dbname',
  1269. 'foo',
  1270. '--command',
  1271. 'ANALYZE',
  1272. ),
  1273. environment={'PGSSLMODE': 'disable'},
  1274. ).once()
  1275. module.restore_data_source_dump(
  1276. hook_config,
  1277. {},
  1278. data_source=hook_config[0],
  1279. dry_run=False,
  1280. extract_process=extract_process,
  1281. connection_params={
  1282. 'hostname': None,
  1283. 'port': None,
  1284. 'username': None,
  1285. 'password': None,
  1286. },
  1287. borgmatic_runtime_directory='/run/borgmatic',
  1288. )
  1289. def test_restore_data_source_dump_with_dry_run_skips_restore():
  1290. hook_config = [{'name': 'foo', 'schemas': None}]
  1291. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1292. 'resolve_credential',
  1293. ).replace_with(lambda value, config: value)
  1294. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1295. flexmock(module).should_receive('make_dump_path')
  1296. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  1297. flexmock(module).should_receive('execute_command_with_processes').never()
  1298. module.restore_data_source_dump(
  1299. hook_config,
  1300. {},
  1301. data_source={'name': 'foo'},
  1302. dry_run=True,
  1303. extract_process=flexmock(),
  1304. connection_params={
  1305. 'hostname': None,
  1306. 'port': None,
  1307. 'username': None,
  1308. 'password': None,
  1309. },
  1310. borgmatic_runtime_directory='/run/borgmatic',
  1311. )
  1312. def test_restore_data_source_dump_without_extract_process_restores_from_disk():
  1313. hook_config = [{'name': 'foo', 'schemas': None}]
  1314. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1315. 'resolve_credential',
  1316. ).replace_with(lambda value, config: value)
  1317. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1318. flexmock(module).should_receive('make_dump_path')
  1319. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
  1320. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1321. (
  1322. 'pg_restore',
  1323. '--no-password',
  1324. '--if-exists',
  1325. '--exit-on-error',
  1326. '--clean',
  1327. '--dbname',
  1328. 'foo',
  1329. '/dump/path',
  1330. ),
  1331. processes=[],
  1332. output_log_level=logging.DEBUG,
  1333. input_file=None,
  1334. environment={'PGSSLMODE': 'disable'},
  1335. ).once()
  1336. flexmock(module).should_receive('execute_command').with_args(
  1337. (
  1338. 'psql',
  1339. '--no-password',
  1340. '--no-psqlrc',
  1341. '--quiet',
  1342. '--dbname',
  1343. 'foo',
  1344. '--command',
  1345. 'ANALYZE',
  1346. ),
  1347. environment={'PGSSLMODE': 'disable'},
  1348. ).once()
  1349. module.restore_data_source_dump(
  1350. hook_config,
  1351. {},
  1352. data_source={'name': 'foo'},
  1353. dry_run=False,
  1354. extract_process=None,
  1355. connection_params={
  1356. 'hostname': None,
  1357. 'port': None,
  1358. 'username': None,
  1359. 'password': None,
  1360. },
  1361. borgmatic_runtime_directory='/run/borgmatic',
  1362. )
  1363. def test_restore_data_source_dump_with_schemas_restores_schemas():
  1364. hook_config = [{'name': 'foo', 'schemas': ['bar', 'baz']}]
  1365. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1366. 'resolve_credential',
  1367. ).replace_with(lambda value, config: value)
  1368. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1369. flexmock(module).should_receive('make_dump_path')
  1370. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
  1371. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1372. (
  1373. 'pg_restore',
  1374. '--no-password',
  1375. '--if-exists',
  1376. '--exit-on-error',
  1377. '--clean',
  1378. '--dbname',
  1379. 'foo',
  1380. '/dump/path',
  1381. '--schema',
  1382. 'bar',
  1383. '--schema',
  1384. 'baz',
  1385. ),
  1386. processes=[],
  1387. output_log_level=logging.DEBUG,
  1388. input_file=None,
  1389. environment={'PGSSLMODE': 'disable'},
  1390. ).once()
  1391. flexmock(module).should_receive('execute_command').with_args(
  1392. (
  1393. 'psql',
  1394. '--no-password',
  1395. '--no-psqlrc',
  1396. '--quiet',
  1397. '--dbname',
  1398. 'foo',
  1399. '--command',
  1400. 'ANALYZE',
  1401. ),
  1402. environment={'PGSSLMODE': 'disable'},
  1403. ).once()
  1404. module.restore_data_source_dump(
  1405. hook_config,
  1406. {},
  1407. data_source=hook_config[0],
  1408. dry_run=False,
  1409. extract_process=None,
  1410. connection_params={
  1411. 'hostname': None,
  1412. 'port': None,
  1413. 'username': None,
  1414. 'password': None,
  1415. },
  1416. borgmatic_runtime_directory='/run/borgmatic',
  1417. )