test_postgresql.py 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488
  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. assert (
  221. module.dump_data_sources(
  222. databases,
  223. {},
  224. config_paths=('test.yaml',),
  225. borgmatic_runtime_directory='/run/borgmatic',
  226. patterns=[],
  227. dry_run=False,
  228. )
  229. == processes
  230. )
  231. def test_dump_data_sources_raises_when_no_database_names_to_dump():
  232. databases = [{'name': 'foo'}, {'name': 'bar'}]
  233. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  234. flexmock(module).should_receive('make_dump_path').and_return('')
  235. flexmock(module).should_receive('database_names_to_dump').and_return(())
  236. with pytest.raises(ValueError):
  237. module.dump_data_sources(
  238. databases,
  239. {},
  240. config_paths=('test.yaml',),
  241. borgmatic_runtime_directory='/run/borgmatic',
  242. patterns=[],
  243. dry_run=False,
  244. )
  245. def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
  246. databases = [{'name': 'foo'}, {'name': 'bar'}]
  247. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  248. flexmock(module).should_receive('make_dump_path').and_return('')
  249. flexmock(module).should_receive('database_names_to_dump').and_return(())
  250. assert (
  251. module.dump_data_sources(
  252. databases,
  253. {},
  254. config_paths=('test.yaml',),
  255. borgmatic_runtime_directory='/run/borgmatic',
  256. patterns=[],
  257. dry_run=True,
  258. )
  259. == []
  260. )
  261. def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
  262. databases = [{'name': 'foo'}, {'name': 'bar'}]
  263. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  264. flexmock(module).should_receive('make_dump_path').and_return('')
  265. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  266. ('bar',),
  267. )
  268. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  269. 'databases/localhost/foo',
  270. ).and_return('databases/localhost/bar')
  271. flexmock(module.os.path).should_receive('exists').and_return(True)
  272. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  273. flexmock(module).should_receive('execute_command').never()
  274. assert (
  275. module.dump_data_sources(
  276. databases,
  277. {},
  278. config_paths=('test.yaml',),
  279. borgmatic_runtime_directory='/run/borgmatic',
  280. patterns=[],
  281. dry_run=False,
  282. )
  283. == []
  284. )
  285. def test_dump_data_sources_with_dry_run_skips_pg_dump():
  286. databases = [{'name': 'foo'}, {'name': 'bar'}]
  287. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  288. flexmock(module).should_receive('make_dump_path').and_return('')
  289. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  290. ('bar',),
  291. )
  292. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  293. 'databases/localhost/foo',
  294. ).and_return('databases/localhost/bar')
  295. flexmock(module.os.path).should_receive('exists').and_return(False)
  296. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  297. 'resolve_credential',
  298. ).replace_with(lambda value, config: value)
  299. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  300. flexmock(module).should_receive('execute_command').never()
  301. assert (
  302. module.dump_data_sources(
  303. databases,
  304. {},
  305. config_paths=('test.yaml',),
  306. borgmatic_runtime_directory='/run/borgmatic',
  307. patterns=[],
  308. dry_run=True,
  309. )
  310. == []
  311. )
  312. def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
  313. databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
  314. process = flexmock()
  315. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  316. flexmock(module).should_receive('make_dump_path').and_return('')
  317. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  318. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  319. 'databases/database.example.org/foo',
  320. )
  321. flexmock(module.os.path).should_receive('exists').and_return(False)
  322. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  323. 'resolve_credential',
  324. ).replace_with(lambda value, config: value)
  325. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  326. flexmock(module).should_receive('execute_command').with_args(
  327. (
  328. 'pg_dump',
  329. '--no-password',
  330. '--clean',
  331. '--if-exists',
  332. '--host',
  333. 'database.example.org',
  334. '--port',
  335. '5433',
  336. '--format',
  337. 'custom',
  338. 'foo',
  339. '>',
  340. 'databases/database.example.org/foo',
  341. ),
  342. shell=True,
  343. environment={'PGSSLMODE': 'disable'},
  344. run_to_completion=False,
  345. ).and_return(process).once()
  346. assert module.dump_data_sources(
  347. databases,
  348. {},
  349. config_paths=('test.yaml',),
  350. borgmatic_runtime_directory='/run/borgmatic',
  351. patterns=[],
  352. dry_run=False,
  353. ) == [process]
  354. def test_dump_data_sources_runs_pg_dump_with_username_and_password():
  355. databases = [{'name': 'foo', 'username': 'postgres', 'password': 'trustsome1'}]
  356. process = flexmock()
  357. flexmock(module).should_receive('make_environment').and_return(
  358. {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  359. )
  360. flexmock(module).should_receive('make_dump_path').and_return('')
  361. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  362. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  363. 'databases/localhost/foo',
  364. )
  365. flexmock(module.os.path).should_receive('exists').and_return(False)
  366. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  367. 'resolve_credential',
  368. ).replace_with(lambda value, config: value)
  369. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  370. flexmock(module).should_receive('execute_command').with_args(
  371. (
  372. 'pg_dump',
  373. '--no-password',
  374. '--clean',
  375. '--if-exists',
  376. '--username',
  377. 'postgres',
  378. '--format',
  379. 'custom',
  380. 'foo',
  381. '>',
  382. 'databases/localhost/foo',
  383. ),
  384. shell=True,
  385. environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  386. run_to_completion=False,
  387. ).and_return(process).once()
  388. assert module.dump_data_sources(
  389. databases,
  390. {},
  391. config_paths=('test.yaml',),
  392. borgmatic_runtime_directory='/run/borgmatic',
  393. patterns=[],
  394. dry_run=False,
  395. ) == [process]
  396. def test_dump_data_sources_with_username_injection_attack_gets_escaped():
  397. databases = [{'name': 'foo', 'username': 'postgres; naughty-command', 'password': 'trustsome1'}]
  398. process = flexmock()
  399. flexmock(module).should_receive('make_environment').and_return(
  400. {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  401. )
  402. flexmock(module).should_receive('make_dump_path').and_return('')
  403. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  404. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  405. 'databases/localhost/foo',
  406. )
  407. flexmock(module.os.path).should_receive('exists').and_return(False)
  408. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  409. 'resolve_credential',
  410. ).replace_with(lambda value, config: value)
  411. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  412. flexmock(module).should_receive('execute_command').with_args(
  413. (
  414. 'pg_dump',
  415. '--no-password',
  416. '--clean',
  417. '--if-exists',
  418. '--username',
  419. "'postgres; naughty-command'",
  420. '--format',
  421. 'custom',
  422. 'foo',
  423. '>',
  424. 'databases/localhost/foo',
  425. ),
  426. shell=True,
  427. environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  428. run_to_completion=False,
  429. ).and_return(process).once()
  430. assert module.dump_data_sources(
  431. databases,
  432. {},
  433. config_paths=('test.yaml',),
  434. borgmatic_runtime_directory='/run/borgmatic',
  435. patterns=[],
  436. dry_run=False,
  437. ) == [process]
  438. def test_dump_data_sources_runs_pg_dump_with_directory_format():
  439. databases = [{'name': 'foo', 'format': 'directory'}]
  440. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  441. flexmock(module).should_receive('make_dump_path').and_return('')
  442. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  443. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  444. 'databases/localhost/foo',
  445. )
  446. flexmock(module.os.path).should_receive('exists').and_return(False)
  447. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  448. 'resolve_credential',
  449. ).replace_with(lambda value, config: value)
  450. flexmock(module.dump).should_receive('create_parent_directory_for_dump')
  451. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  452. flexmock(module).should_receive('execute_command').with_args(
  453. (
  454. 'pg_dump',
  455. '--no-password',
  456. '--clean',
  457. '--if-exists',
  458. '--format',
  459. 'directory',
  460. '--file',
  461. 'databases/localhost/foo',
  462. 'foo',
  463. ),
  464. shell=True,
  465. environment={'PGSSLMODE': 'disable'},
  466. ).and_return(flexmock()).once()
  467. assert (
  468. module.dump_data_sources(
  469. databases,
  470. {},
  471. config_paths=('test.yaml',),
  472. borgmatic_runtime_directory='/run/borgmatic',
  473. patterns=[],
  474. dry_run=False,
  475. )
  476. == []
  477. )
  478. def test_dump_data_sources_runs_pg_dump_with_string_compression():
  479. databases = [{'name': 'foo', 'compression': 'winrar'}]
  480. processes = [flexmock()]
  481. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  482. flexmock(module).should_receive('make_dump_path').and_return('')
  483. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  484. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  485. 'databases/localhost/foo',
  486. )
  487. flexmock(module.os.path).should_receive('exists').and_return(False)
  488. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  489. 'resolve_credential',
  490. ).replace_with(lambda value, config: value)
  491. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  492. flexmock(module).should_receive('execute_command').with_args(
  493. (
  494. 'pg_dump',
  495. '--no-password',
  496. '--clean',
  497. '--if-exists',
  498. '--format',
  499. 'custom',
  500. '--compress',
  501. 'winrar',
  502. 'foo',
  503. '>',
  504. 'databases/localhost/foo',
  505. ),
  506. shell=True,
  507. environment={'PGSSLMODE': 'disable'},
  508. run_to_completion=False,
  509. ).and_return(processes[0]).once()
  510. assert (
  511. module.dump_data_sources(
  512. databases,
  513. {},
  514. config_paths=('test.yaml',),
  515. borgmatic_runtime_directory='/run/borgmatic',
  516. patterns=[],
  517. dry_run=False,
  518. )
  519. == processes
  520. )
  521. def test_dump_data_sources_runs_pg_dump_with_integer_compression():
  522. databases = [{'name': 'foo', 'compression': 0}]
  523. processes = [flexmock()]
  524. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  525. flexmock(module).should_receive('make_dump_path').and_return('')
  526. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  527. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  528. 'databases/localhost/foo',
  529. )
  530. flexmock(module.os.path).should_receive('exists').and_return(False)
  531. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  532. 'resolve_credential',
  533. ).replace_with(lambda value, config: value)
  534. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  535. flexmock(module).should_receive('execute_command').with_args(
  536. (
  537. 'pg_dump',
  538. '--no-password',
  539. '--clean',
  540. '--if-exists',
  541. '--format',
  542. 'custom',
  543. '--compress',
  544. '0',
  545. 'foo',
  546. '>',
  547. 'databases/localhost/foo',
  548. ),
  549. shell=True,
  550. environment={'PGSSLMODE': 'disable'},
  551. run_to_completion=False,
  552. ).and_return(processes[0]).once()
  553. assert (
  554. module.dump_data_sources(
  555. databases,
  556. {},
  557. config_paths=('test.yaml',),
  558. borgmatic_runtime_directory='/run/borgmatic',
  559. patterns=[],
  560. dry_run=False,
  561. )
  562. == processes
  563. )
  564. def test_dump_data_sources_runs_pg_dump_with_options():
  565. databases = [{'name': 'foo', 'options': '--stuff=such'}]
  566. process = flexmock()
  567. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  568. flexmock(module).should_receive('make_dump_path').and_return('')
  569. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  570. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  571. 'databases/localhost/foo',
  572. )
  573. flexmock(module.os.path).should_receive('exists').and_return(False)
  574. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  575. 'resolve_credential',
  576. ).replace_with(lambda value, config: value)
  577. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  578. flexmock(module).should_receive('execute_command').with_args(
  579. (
  580. 'pg_dump',
  581. '--no-password',
  582. '--clean',
  583. '--if-exists',
  584. '--format',
  585. 'custom',
  586. '--stuff=such',
  587. 'foo',
  588. '>',
  589. 'databases/localhost/foo',
  590. ),
  591. shell=True,
  592. environment={'PGSSLMODE': 'disable'},
  593. run_to_completion=False,
  594. ).and_return(process).once()
  595. assert module.dump_data_sources(
  596. databases,
  597. {},
  598. config_paths=('test.yaml',),
  599. borgmatic_runtime_directory='/run/borgmatic',
  600. patterns=[],
  601. dry_run=False,
  602. ) == [process]
  603. def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
  604. databases = [{'name': 'all'}]
  605. process = flexmock()
  606. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  607. flexmock(module).should_receive('make_dump_path').and_return('')
  608. flexmock(module).should_receive('database_names_to_dump').and_return(('all',))
  609. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  610. 'databases/localhost/all',
  611. )
  612. flexmock(module.os.path).should_receive('exists').and_return(False)
  613. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  614. 'resolve_credential',
  615. ).replace_with(lambda value, config: value)
  616. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  617. flexmock(module).should_receive('execute_command').with_args(
  618. ('pg_dumpall', '--no-password', '--clean', '--if-exists', '>', 'databases/localhost/all'),
  619. shell=True,
  620. environment={'PGSSLMODE': 'disable'},
  621. run_to_completion=False,
  622. ).and_return(process).once()
  623. assert module.dump_data_sources(
  624. databases,
  625. {},
  626. config_paths=('test.yaml',),
  627. borgmatic_runtime_directory='/run/borgmatic',
  628. patterns=[],
  629. dry_run=False,
  630. ) == [process]
  631. def test_dump_data_sources_runs_non_default_pg_dump():
  632. databases = [{'name': 'foo', 'pg_dump_command': 'special_pg_dump --compress *'}]
  633. process = flexmock()
  634. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  635. flexmock(module).should_receive('make_dump_path').and_return('')
  636. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  637. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  638. 'databases/localhost/foo',
  639. )
  640. flexmock(module.os.path).should_receive('exists').and_return(False)
  641. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  642. 'resolve_credential',
  643. ).replace_with(lambda value, config: value)
  644. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  645. flexmock(module).should_receive('execute_command').with_args(
  646. (
  647. 'special_pg_dump',
  648. '--compress',
  649. "'*'", # Should get shell escaped to prevent injection attacks.
  650. '--no-password',
  651. '--clean',
  652. '--if-exists',
  653. '--format',
  654. 'custom',
  655. 'foo',
  656. '>',
  657. 'databases/localhost/foo',
  658. ),
  659. shell=True,
  660. environment={'PGSSLMODE': 'disable'},
  661. run_to_completion=False,
  662. ).and_return(process).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_restore_data_source_dump_runs_pg_restore():
  672. hook_config = [{'name': 'foo', 'schemas': None}, {'name': 'bar'}]
  673. extract_process = flexmock(stdout=flexmock())
  674. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  675. 'resolve_credential',
  676. ).replace_with(lambda value, config: value)
  677. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  678. flexmock(module).should_receive('make_dump_path')
  679. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  680. flexmock(module).should_receive('execute_command_with_processes').with_args(
  681. (
  682. 'pg_restore',
  683. '--no-password',
  684. '--if-exists',
  685. '--exit-on-error',
  686. '--clean',
  687. '--dbname',
  688. 'foo',
  689. ),
  690. processes=[extract_process],
  691. output_log_level=logging.DEBUG,
  692. input_file=extract_process.stdout,
  693. environment={'PGSSLMODE': 'disable'},
  694. ).once()
  695. flexmock(module).should_receive('execute_command').with_args(
  696. (
  697. 'psql',
  698. '--no-password',
  699. '--no-psqlrc',
  700. '--quiet',
  701. '--dbname',
  702. 'foo',
  703. '--command',
  704. 'ANALYZE',
  705. ),
  706. environment={'PGSSLMODE': 'disable'},
  707. ).once()
  708. module.restore_data_source_dump(
  709. hook_config,
  710. {},
  711. data_source={'name': 'foo'},
  712. dry_run=False,
  713. extract_process=extract_process,
  714. connection_params={
  715. 'hostname': None,
  716. 'port': None,
  717. 'username': None,
  718. 'password': None,
  719. },
  720. borgmatic_runtime_directory='/run/borgmatic',
  721. )
  722. def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
  723. hook_config = [
  724. {'name': 'foo', 'hostname': 'database.example.org', 'port': 5433, 'schemas': None},
  725. ]
  726. extract_process = flexmock(stdout=flexmock())
  727. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  728. 'resolve_credential',
  729. ).replace_with(lambda value, config: value)
  730. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  731. flexmock(module).should_receive('make_dump_path')
  732. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  733. flexmock(module).should_receive('execute_command_with_processes').with_args(
  734. (
  735. 'pg_restore',
  736. '--no-password',
  737. '--if-exists',
  738. '--exit-on-error',
  739. '--clean',
  740. '--dbname',
  741. 'foo',
  742. '--host',
  743. 'database.example.org',
  744. '--port',
  745. '5433',
  746. ),
  747. processes=[extract_process],
  748. output_log_level=logging.DEBUG,
  749. input_file=extract_process.stdout,
  750. environment={'PGSSLMODE': 'disable'},
  751. ).once()
  752. flexmock(module).should_receive('execute_command').with_args(
  753. (
  754. 'psql',
  755. '--no-password',
  756. '--no-psqlrc',
  757. '--quiet',
  758. '--host',
  759. 'database.example.org',
  760. '--port',
  761. '5433',
  762. '--dbname',
  763. 'foo',
  764. '--command',
  765. 'ANALYZE',
  766. ),
  767. environment={'PGSSLMODE': 'disable'},
  768. ).once()
  769. module.restore_data_source_dump(
  770. hook_config,
  771. {},
  772. data_source=hook_config[0],
  773. dry_run=False,
  774. extract_process=extract_process,
  775. connection_params={
  776. 'hostname': None,
  777. 'port': None,
  778. 'username': None,
  779. 'password': None,
  780. },
  781. borgmatic_runtime_directory='/run/borgmatic',
  782. )
  783. def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
  784. hook_config = [
  785. {'name': 'foo', 'username': 'postgres', 'password': 'trustsome1', 'schemas': None},
  786. ]
  787. extract_process = flexmock(stdout=flexmock())
  788. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  789. 'resolve_credential',
  790. ).replace_with(lambda value, config: value)
  791. flexmock(module).should_receive('make_environment').and_return(
  792. {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  793. )
  794. flexmock(module).should_receive('make_dump_path')
  795. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  796. flexmock(module).should_receive('execute_command_with_processes').with_args(
  797. (
  798. 'pg_restore',
  799. '--no-password',
  800. '--if-exists',
  801. '--exit-on-error',
  802. '--clean',
  803. '--dbname',
  804. 'foo',
  805. '--username',
  806. 'postgres',
  807. ),
  808. processes=[extract_process],
  809. output_log_level=logging.DEBUG,
  810. input_file=extract_process.stdout,
  811. environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  812. ).once()
  813. flexmock(module).should_receive('execute_command').with_args(
  814. (
  815. 'psql',
  816. '--no-password',
  817. '--no-psqlrc',
  818. '--quiet',
  819. '--username',
  820. 'postgres',
  821. '--dbname',
  822. 'foo',
  823. '--command',
  824. 'ANALYZE',
  825. ),
  826. environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  827. ).once()
  828. module.restore_data_source_dump(
  829. hook_config,
  830. {},
  831. data_source=hook_config[0],
  832. dry_run=False,
  833. extract_process=extract_process,
  834. connection_params={
  835. 'hostname': None,
  836. 'port': None,
  837. 'username': None,
  838. 'password': None,
  839. },
  840. borgmatic_runtime_directory='/run/borgmatic',
  841. )
  842. def test_restore_data_source_dump_with_connection_params_uses_connection_params_for_restore():
  843. hook_config = [
  844. {
  845. 'name': 'foo',
  846. 'hostname': 'database.example.org',
  847. 'port': 5433,
  848. 'username': 'postgres',
  849. 'password': 'trustsome1',
  850. 'restore_hostname': 'restorehost',
  851. 'restore_port': 'restoreport',
  852. 'restore_username': 'restoreusername',
  853. 'restore_password': 'restorepassword',
  854. 'schemas': None,
  855. },
  856. ]
  857. extract_process = flexmock(stdout=flexmock())
  858. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  859. 'resolve_credential',
  860. ).replace_with(lambda value, config: value)
  861. flexmock(module).should_receive('make_environment').and_return(
  862. {'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
  863. )
  864. flexmock(module).should_receive('make_dump_path')
  865. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  866. flexmock(module).should_receive('execute_command_with_processes').with_args(
  867. (
  868. 'pg_restore',
  869. '--no-password',
  870. '--if-exists',
  871. '--exit-on-error',
  872. '--clean',
  873. '--dbname',
  874. 'foo',
  875. '--host',
  876. 'clihost',
  877. '--port',
  878. 'cliport',
  879. '--username',
  880. 'cliusername',
  881. ),
  882. processes=[extract_process],
  883. output_log_level=logging.DEBUG,
  884. input_file=extract_process.stdout,
  885. environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
  886. ).once()
  887. flexmock(module).should_receive('execute_command').with_args(
  888. (
  889. 'psql',
  890. '--no-password',
  891. '--no-psqlrc',
  892. '--quiet',
  893. '--host',
  894. 'clihost',
  895. '--port',
  896. 'cliport',
  897. '--username',
  898. 'cliusername',
  899. '--dbname',
  900. 'foo',
  901. '--command',
  902. 'ANALYZE',
  903. ),
  904. environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
  905. ).once()
  906. module.restore_data_source_dump(
  907. hook_config,
  908. {},
  909. data_source={'name': 'foo'},
  910. dry_run=False,
  911. extract_process=extract_process,
  912. connection_params={
  913. 'hostname': 'clihost',
  914. 'port': 'cliport',
  915. 'username': 'cliusername',
  916. 'password': 'clipassword',
  917. },
  918. borgmatic_runtime_directory='/run/borgmatic',
  919. )
  920. def test_restore_data_source_dump_without_connection_params_uses_restore_params_in_config_for_restore():
  921. hook_config = [
  922. {
  923. 'name': 'foo',
  924. 'hostname': 'database.example.org',
  925. 'port': 5433,
  926. 'username': 'postgres',
  927. 'password': 'trustsome1',
  928. 'schemas': None,
  929. 'restore_hostname': 'restorehost',
  930. 'restore_port': 'restoreport',
  931. 'restore_username': 'restoreusername',
  932. 'restore_password': 'restorepassword',
  933. },
  934. ]
  935. extract_process = flexmock(stdout=flexmock())
  936. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  937. 'resolve_credential',
  938. ).replace_with(lambda value, config: value)
  939. flexmock(module).should_receive('make_environment').and_return(
  940. {'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
  941. )
  942. flexmock(module).should_receive('make_dump_path')
  943. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  944. flexmock(module).should_receive('execute_command_with_processes').with_args(
  945. (
  946. 'pg_restore',
  947. '--no-password',
  948. '--if-exists',
  949. '--exit-on-error',
  950. '--clean',
  951. '--dbname',
  952. 'foo',
  953. '--host',
  954. 'restorehost',
  955. '--port',
  956. 'restoreport',
  957. '--username',
  958. 'restoreusername',
  959. ),
  960. processes=[extract_process],
  961. output_log_level=logging.DEBUG,
  962. input_file=extract_process.stdout,
  963. environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
  964. ).once()
  965. flexmock(module).should_receive('execute_command').with_args(
  966. (
  967. 'psql',
  968. '--no-password',
  969. '--no-psqlrc',
  970. '--quiet',
  971. '--host',
  972. 'restorehost',
  973. '--port',
  974. 'restoreport',
  975. '--username',
  976. 'restoreusername',
  977. '--dbname',
  978. 'foo',
  979. '--command',
  980. 'ANALYZE',
  981. ),
  982. environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
  983. ).once()
  984. module.restore_data_source_dump(
  985. hook_config,
  986. {},
  987. data_source=hook_config[0],
  988. dry_run=False,
  989. extract_process=extract_process,
  990. connection_params={
  991. 'hostname': None,
  992. 'port': None,
  993. 'username': None,
  994. 'password': None,
  995. },
  996. borgmatic_runtime_directory='/run/borgmatic',
  997. )
  998. def test_restore_data_source_dump_runs_pg_restore_with_options():
  999. hook_config = [
  1000. {
  1001. 'name': 'foo',
  1002. 'restore_options': '--harder',
  1003. 'analyze_options': '--smarter',
  1004. 'schemas': None,
  1005. },
  1006. ]
  1007. extract_process = flexmock(stdout=flexmock())
  1008. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1009. 'resolve_credential',
  1010. ).replace_with(lambda value, config: value)
  1011. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1012. flexmock(module).should_receive('make_dump_path')
  1013. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  1014. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1015. (
  1016. 'pg_restore',
  1017. '--no-password',
  1018. '--if-exists',
  1019. '--exit-on-error',
  1020. '--clean',
  1021. '--dbname',
  1022. 'foo',
  1023. '--harder',
  1024. ),
  1025. processes=[extract_process],
  1026. output_log_level=logging.DEBUG,
  1027. input_file=extract_process.stdout,
  1028. environment={'PGSSLMODE': 'disable'},
  1029. ).once()
  1030. flexmock(module).should_receive('execute_command').with_args(
  1031. (
  1032. 'psql',
  1033. '--no-password',
  1034. '--no-psqlrc',
  1035. '--quiet',
  1036. '--dbname',
  1037. 'foo',
  1038. '--smarter',
  1039. '--command',
  1040. 'ANALYZE',
  1041. ),
  1042. environment={'PGSSLMODE': 'disable'},
  1043. ).once()
  1044. module.restore_data_source_dump(
  1045. hook_config,
  1046. {},
  1047. data_source=hook_config[0],
  1048. dry_run=False,
  1049. extract_process=extract_process,
  1050. connection_params={
  1051. 'hostname': None,
  1052. 'port': None,
  1053. 'username': None,
  1054. 'password': None,
  1055. },
  1056. borgmatic_runtime_directory='/run/borgmatic',
  1057. )
  1058. def test_restore_data_source_dump_runs_psql_for_all_database_dump():
  1059. hook_config = [{'name': 'all', 'schemas': None}]
  1060. extract_process = flexmock(stdout=flexmock())
  1061. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1062. 'resolve_credential',
  1063. ).replace_with(lambda value, config: value)
  1064. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1065. flexmock(module).should_receive('make_dump_path')
  1066. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  1067. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1068. (
  1069. 'psql',
  1070. '--no-password',
  1071. '--no-psqlrc',
  1072. ),
  1073. processes=[extract_process],
  1074. output_log_level=logging.DEBUG,
  1075. input_file=extract_process.stdout,
  1076. environment={'PGSSLMODE': 'disable'},
  1077. ).once()
  1078. flexmock(module).should_receive('execute_command').with_args(
  1079. ('psql', '--no-password', '--no-psqlrc', '--quiet', '--command', 'ANALYZE'),
  1080. environment={'PGSSLMODE': 'disable'},
  1081. ).once()
  1082. module.restore_data_source_dump(
  1083. hook_config,
  1084. {},
  1085. data_source={'name': 'all'},
  1086. dry_run=False,
  1087. extract_process=extract_process,
  1088. connection_params={
  1089. 'hostname': None,
  1090. 'port': None,
  1091. 'username': None,
  1092. 'password': None,
  1093. },
  1094. borgmatic_runtime_directory='/run/borgmatic',
  1095. )
  1096. def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
  1097. hook_config = [{'name': 'foo', 'format': 'plain', 'schemas': None}]
  1098. extract_process = flexmock(stdout=flexmock())
  1099. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1100. 'resolve_credential',
  1101. ).replace_with(lambda value, config: value)
  1102. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1103. flexmock(module).should_receive('make_dump_path')
  1104. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  1105. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1106. ('psql', '--no-password', '--no-psqlrc', '--dbname', 'foo'),
  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. '--command',
  1121. 'ANALYZE',
  1122. ),
  1123. environment={'PGSSLMODE': 'disable'},
  1124. ).once()
  1125. module.restore_data_source_dump(
  1126. hook_config,
  1127. {},
  1128. data_source=hook_config[0],
  1129. dry_run=False,
  1130. extract_process=extract_process,
  1131. connection_params={
  1132. 'hostname': None,
  1133. 'port': None,
  1134. 'username': None,
  1135. 'password': None,
  1136. },
  1137. borgmatic_runtime_directory='/run/borgmatic',
  1138. )
  1139. def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
  1140. hook_config = [
  1141. {
  1142. 'name': 'foo',
  1143. 'pg_restore_command': 'docker exec --workdir * mycontainer pg_restore',
  1144. 'psql_command': 'docker exec --workdir * mycontainer psql',
  1145. 'schemas': None,
  1146. },
  1147. ]
  1148. extract_process = flexmock(stdout=flexmock())
  1149. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1150. 'resolve_credential',
  1151. ).replace_with(lambda value, config: value)
  1152. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1153. flexmock(module).should_receive('make_dump_path')
  1154. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  1155. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1156. (
  1157. 'docker',
  1158. 'exec',
  1159. '--workdir',
  1160. "'*'", # Should get shell escaped to prevent injection attacks.
  1161. 'mycontainer',
  1162. 'pg_restore',
  1163. '--no-password',
  1164. '--if-exists',
  1165. '--exit-on-error',
  1166. '--clean',
  1167. '--dbname',
  1168. 'foo',
  1169. ),
  1170. processes=[extract_process],
  1171. output_log_level=logging.DEBUG,
  1172. input_file=extract_process.stdout,
  1173. environment={'PGSSLMODE': 'disable'},
  1174. ).once()
  1175. flexmock(module).should_receive('execute_command').with_args(
  1176. (
  1177. 'docker',
  1178. 'exec',
  1179. '--workdir',
  1180. "'*'", # Should get shell escaped to prevent injection attacks.
  1181. 'mycontainer',
  1182. 'psql',
  1183. '--no-password',
  1184. '--no-psqlrc',
  1185. '--quiet',
  1186. '--dbname',
  1187. 'foo',
  1188. '--command',
  1189. 'ANALYZE',
  1190. ),
  1191. environment={'PGSSLMODE': 'disable'},
  1192. ).once()
  1193. module.restore_data_source_dump(
  1194. hook_config,
  1195. {},
  1196. data_source=hook_config[0],
  1197. dry_run=False,
  1198. extract_process=extract_process,
  1199. connection_params={
  1200. 'hostname': None,
  1201. 'port': None,
  1202. 'username': None,
  1203. 'password': None,
  1204. },
  1205. borgmatic_runtime_directory='/run/borgmatic',
  1206. )
  1207. def test_restore_data_source_dump_with_dry_run_skips_restore():
  1208. hook_config = [{'name': 'foo', 'schemas': None}]
  1209. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1210. 'resolve_credential',
  1211. ).replace_with(lambda value, config: value)
  1212. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1213. flexmock(module).should_receive('make_dump_path')
  1214. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  1215. flexmock(module).should_receive('execute_command_with_processes').never()
  1216. module.restore_data_source_dump(
  1217. hook_config,
  1218. {},
  1219. data_source={'name': 'foo'},
  1220. dry_run=True,
  1221. extract_process=flexmock(),
  1222. connection_params={
  1223. 'hostname': None,
  1224. 'port': None,
  1225. 'username': None,
  1226. 'password': None,
  1227. },
  1228. borgmatic_runtime_directory='/run/borgmatic',
  1229. )
  1230. def test_restore_data_source_dump_without_extract_process_restores_from_disk():
  1231. hook_config = [{'name': 'foo', 'schemas': None}]
  1232. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1233. 'resolve_credential',
  1234. ).replace_with(lambda value, config: value)
  1235. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1236. flexmock(module).should_receive('make_dump_path')
  1237. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
  1238. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1239. (
  1240. 'pg_restore',
  1241. '--no-password',
  1242. '--if-exists',
  1243. '--exit-on-error',
  1244. '--clean',
  1245. '--dbname',
  1246. 'foo',
  1247. '/dump/path',
  1248. ),
  1249. processes=[],
  1250. output_log_level=logging.DEBUG,
  1251. input_file=None,
  1252. environment={'PGSSLMODE': 'disable'},
  1253. ).once()
  1254. flexmock(module).should_receive('execute_command').with_args(
  1255. (
  1256. 'psql',
  1257. '--no-password',
  1258. '--no-psqlrc',
  1259. '--quiet',
  1260. '--dbname',
  1261. 'foo',
  1262. '--command',
  1263. 'ANALYZE',
  1264. ),
  1265. environment={'PGSSLMODE': 'disable'},
  1266. ).once()
  1267. module.restore_data_source_dump(
  1268. hook_config,
  1269. {},
  1270. data_source={'name': 'foo'},
  1271. dry_run=False,
  1272. extract_process=None,
  1273. connection_params={
  1274. 'hostname': None,
  1275. 'port': None,
  1276. 'username': None,
  1277. 'password': None,
  1278. },
  1279. borgmatic_runtime_directory='/run/borgmatic',
  1280. )
  1281. def test_restore_data_source_dump_with_schemas_restores_schemas():
  1282. hook_config = [{'name': 'foo', 'schemas': ['bar', 'baz']}]
  1283. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1284. 'resolve_credential',
  1285. ).replace_with(lambda value, config: value)
  1286. flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
  1287. flexmock(module).should_receive('make_dump_path')
  1288. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
  1289. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1290. (
  1291. 'pg_restore',
  1292. '--no-password',
  1293. '--if-exists',
  1294. '--exit-on-error',
  1295. '--clean',
  1296. '--dbname',
  1297. 'foo',
  1298. '/dump/path',
  1299. '--schema',
  1300. 'bar',
  1301. '--schema',
  1302. 'baz',
  1303. ),
  1304. processes=[],
  1305. output_log_level=logging.DEBUG,
  1306. input_file=None,
  1307. environment={'PGSSLMODE': 'disable'},
  1308. ).once()
  1309. flexmock(module).should_receive('execute_command').with_args(
  1310. (
  1311. 'psql',
  1312. '--no-password',
  1313. '--no-psqlrc',
  1314. '--quiet',
  1315. '--dbname',
  1316. 'foo',
  1317. '--command',
  1318. 'ANALYZE',
  1319. ),
  1320. environment={'PGSSLMODE': 'disable'},
  1321. ).once()
  1322. module.restore_data_source_dump(
  1323. hook_config,
  1324. {},
  1325. data_source=hook_config[0],
  1326. dry_run=False,
  1327. extract_process=None,
  1328. connection_params={
  1329. 'hostname': None,
  1330. 'port': None,
  1331. 'username': None,
  1332. 'password': None,
  1333. },
  1334. borgmatic_runtime_directory='/run/borgmatic',
  1335. )