test_postgresql.py 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192
  1. import logging
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.hooks import postgresql as module
  5. def test_make_extra_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. 'PGPASSWORD': 'pass',
  17. 'PGSSLMODE': 'require',
  18. 'PGSSLCERT': 'cert.crt',
  19. 'PGSSLKEY': 'key.key',
  20. 'PGSSLROOTCERT': 'root.crt',
  21. 'PGSSLCRL': 'crl.crl',
  22. }
  23. extra_env = module.make_extra_environment(database)
  24. assert extra_env == expected
  25. def test_make_extra_environment_with_cli_password_sets_correct_password():
  26. database = {'name': 'foo', 'restore_password': 'trustsome1', 'password': 'anotherpassword'}
  27. extra = module.make_extra_environment(
  28. database, restore_connection_params={'password': 'clipassword'}
  29. )
  30. assert extra['PGPASSWORD'] == 'clipassword'
  31. def test_make_extra_environment_without_cli_password_or_configured_password_does_not_set_password():
  32. database = {'name': 'foo'}
  33. extra = module.make_extra_environment(
  34. database, restore_connection_params={'username': 'someone'}
  35. )
  36. assert 'PGPASSWORD' not in extra
  37. def test_make_extra_environment_without_ssl_mode_does_not_set_ssl_mode():
  38. database = {'name': 'foo'}
  39. extra = module.make_extra_environment(database)
  40. assert 'PGSSLMODE' not in extra
  41. def test_database_names_to_dump_passes_through_individual_database_name():
  42. database = {'name': 'foo'}
  43. assert module.database_names_to_dump(database, flexmock(), flexmock(), dry_run=False) == (
  44. 'foo',
  45. )
  46. def test_database_names_to_dump_passes_through_individual_database_name_with_format():
  47. database = {'name': 'foo', 'format': 'custom'}
  48. assert module.database_names_to_dump(database, flexmock(), flexmock(), dry_run=False) == (
  49. 'foo',
  50. )
  51. def test_database_names_to_dump_passes_through_all_without_format():
  52. database = {'name': 'all'}
  53. assert module.database_names_to_dump(database, flexmock(), flexmock(), dry_run=False) == (
  54. 'all',
  55. )
  56. def test_database_names_to_dump_with_all_and_format_and_dry_run_bails():
  57. database = {'name': 'all', 'format': 'custom'}
  58. flexmock(module).should_receive('execute_command_and_capture_output').never()
  59. assert module.database_names_to_dump(database, flexmock(), flexmock(), dry_run=True) == ()
  60. def test_database_names_to_dump_with_all_and_format_lists_databases():
  61. database = {'name': 'all', 'format': 'custom'}
  62. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  63. 'foo,test,\nbar,test,"stuff and such"'
  64. )
  65. assert module.database_names_to_dump(database, flexmock(), flexmock(), dry_run=False) == (
  66. 'foo',
  67. 'bar',
  68. )
  69. def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostname_and_port():
  70. database = {'name': 'all', 'format': 'custom', 'hostname': 'localhost', 'port': 1234}
  71. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  72. (
  73. 'psql',
  74. '--list',
  75. '--no-password',
  76. '--no-psqlrc',
  77. '--csv',
  78. '--tuples-only',
  79. '--host',
  80. 'localhost',
  81. '--port',
  82. '1234',
  83. ),
  84. extra_environment=object,
  85. ).and_return('foo,test,\nbar,test,"stuff and such"')
  86. assert module.database_names_to_dump(database, flexmock(), flexmock(), dry_run=False) == (
  87. 'foo',
  88. 'bar',
  89. )
  90. def test_database_names_to_dump_with_all_and_format_lists_databases_with_username():
  91. database = {'name': 'all', 'format': 'custom', 'username': 'postgres'}
  92. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  93. (
  94. 'psql',
  95. '--list',
  96. '--no-password',
  97. '--no-psqlrc',
  98. '--csv',
  99. '--tuples-only',
  100. '--username',
  101. 'postgres',
  102. ),
  103. extra_environment=object,
  104. ).and_return('foo,test,\nbar,test,"stuff and such"')
  105. assert module.database_names_to_dump(database, flexmock(), flexmock(), dry_run=False) == (
  106. 'foo',
  107. 'bar',
  108. )
  109. def test_database_names_to_dump_with_all_and_format_lists_databases_with_options():
  110. database = {'name': 'all', 'format': 'custom', 'list_options': '--harder'}
  111. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  112. ('psql', '--list', '--no-password', '--no-psqlrc', '--csv', '--tuples-only', '--harder'),
  113. extra_environment=object,
  114. ).and_return('foo,test,\nbar,test,"stuff and such"')
  115. assert module.database_names_to_dump(database, flexmock(), flexmock(), dry_run=False) == (
  116. 'foo',
  117. 'bar',
  118. )
  119. def test_database_names_to_dump_with_all_and_format_excludes_particular_databases():
  120. database = {'name': 'all', 'format': 'custom'}
  121. flexmock(module).should_receive('execute_command_and_capture_output').and_return(
  122. 'foo,test,\ntemplate0,test,blah'
  123. )
  124. assert module.database_names_to_dump(database, flexmock(), flexmock(), dry_run=False) == (
  125. 'foo',
  126. )
  127. def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
  128. database = {
  129. 'name': 'all',
  130. 'format': 'custom',
  131. 'psql_command': 'docker exec --workdir * mycontainer psql',
  132. }
  133. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  134. (
  135. 'docker',
  136. 'exec',
  137. '--workdir',
  138. "'*'", # Should get shell escaped to prevent injection attacks.
  139. 'mycontainer',
  140. 'psql',
  141. '--list',
  142. '--no-password',
  143. '--no-psqlrc',
  144. '--csv',
  145. '--tuples-only',
  146. ),
  147. extra_environment=object,
  148. ).and_return('foo,text').once()
  149. assert module.database_names_to_dump(database, flexmock(), flexmock(), dry_run=False) == (
  150. 'foo',
  151. )
  152. def test_dump_data_sources_runs_pg_dump_for_each_database():
  153. databases = [{'name': 'foo'}, {'name': 'bar'}]
  154. processes = [flexmock(), flexmock()]
  155. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  156. flexmock(module).should_receive('make_dump_path').and_return('')
  157. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  158. ('bar',)
  159. )
  160. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  161. 'databases/localhost/foo'
  162. ).and_return('databases/localhost/bar')
  163. flexmock(module.os.path).should_receive('exists').and_return(False)
  164. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  165. for name, process in zip(('foo', 'bar'), processes):
  166. flexmock(module).should_receive('execute_command').with_args(
  167. (
  168. 'pg_dump',
  169. '--no-password',
  170. '--clean',
  171. '--if-exists',
  172. '--format',
  173. 'custom',
  174. name,
  175. '>',
  176. f'databases/localhost/{name}',
  177. ),
  178. shell=True,
  179. extra_environment={'PGSSLMODE': 'disable'},
  180. run_to_completion=False,
  181. ).and_return(process).once()
  182. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
  183. def test_dump_data_sources_raises_when_no_database_names_to_dump():
  184. databases = [{'name': 'foo'}, {'name': 'bar'}]
  185. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  186. flexmock(module).should_receive('make_dump_path').and_return('')
  187. flexmock(module).should_receive('database_names_to_dump').and_return(())
  188. with pytest.raises(ValueError):
  189. module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False)
  190. def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
  191. databases = [{'name': 'foo'}, {'name': 'bar'}]
  192. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  193. flexmock(module).should_receive('make_dump_path').and_return('')
  194. flexmock(module).should_receive('database_names_to_dump').and_return(())
  195. module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
  196. def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
  197. databases = [{'name': 'foo'}, {'name': 'bar'}]
  198. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  199. flexmock(module).should_receive('make_dump_path').and_return('')
  200. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  201. ('bar',)
  202. )
  203. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  204. 'databases/localhost/foo'
  205. ).and_return('databases/localhost/bar')
  206. flexmock(module.os.path).should_receive('exists').and_return(True)
  207. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  208. flexmock(module).should_receive('execute_command').never()
  209. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
  210. def test_dump_data_sources_with_dry_run_skips_pg_dump():
  211. databases = [{'name': 'foo'}, {'name': 'bar'}]
  212. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  213. flexmock(module).should_receive('make_dump_path').and_return('')
  214. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  215. ('bar',)
  216. )
  217. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  218. 'databases/localhost/foo'
  219. ).and_return('databases/localhost/bar')
  220. flexmock(module.os.path).should_receive('exists').and_return(False)
  221. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  222. flexmock(module).should_receive('execute_command').never()
  223. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
  224. def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
  225. databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
  226. process = flexmock()
  227. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  228. flexmock(module).should_receive('make_dump_path').and_return('')
  229. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  230. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  231. 'databases/database.example.org/foo'
  232. )
  233. flexmock(module.os.path).should_receive('exists').and_return(False)
  234. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  235. flexmock(module).should_receive('execute_command').with_args(
  236. (
  237. 'pg_dump',
  238. '--no-password',
  239. '--clean',
  240. '--if-exists',
  241. '--host',
  242. 'database.example.org',
  243. '--port',
  244. '5433',
  245. '--format',
  246. 'custom',
  247. 'foo',
  248. '>',
  249. 'databases/database.example.org/foo',
  250. ),
  251. shell=True,
  252. extra_environment={'PGSSLMODE': 'disable'},
  253. run_to_completion=False,
  254. ).and_return(process).once()
  255. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
  256. def test_dump_data_sources_runs_pg_dump_with_username_and_password():
  257. databases = [{'name': 'foo', 'username': 'postgres', 'password': 'trustsome1'}]
  258. process = flexmock()
  259. flexmock(module).should_receive('make_extra_environment').and_return(
  260. {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
  261. )
  262. flexmock(module).should_receive('make_dump_path').and_return('')
  263. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  264. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  265. 'databases/localhost/foo'
  266. )
  267. flexmock(module.os.path).should_receive('exists').and_return(False)
  268. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  269. flexmock(module).should_receive('execute_command').with_args(
  270. (
  271. 'pg_dump',
  272. '--no-password',
  273. '--clean',
  274. '--if-exists',
  275. '--username',
  276. 'postgres',
  277. '--format',
  278. 'custom',
  279. 'foo',
  280. '>',
  281. 'databases/localhost/foo',
  282. ),
  283. shell=True,
  284. extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  285. run_to_completion=False,
  286. ).and_return(process).once()
  287. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
  288. def test_dump_data_sources_with_username_injection_attack_gets_escaped():
  289. databases = [{'name': 'foo', 'username': 'postgres; naughty-command', 'password': 'trustsome1'}]
  290. process = flexmock()
  291. flexmock(module).should_receive('make_extra_environment').and_return(
  292. {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
  293. )
  294. flexmock(module).should_receive('make_dump_path').and_return('')
  295. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  296. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  297. 'databases/localhost/foo'
  298. )
  299. flexmock(module.os.path).should_receive('exists').and_return(False)
  300. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  301. flexmock(module).should_receive('execute_command').with_args(
  302. (
  303. 'pg_dump',
  304. '--no-password',
  305. '--clean',
  306. '--if-exists',
  307. '--username',
  308. "'postgres; naughty-command'",
  309. '--format',
  310. 'custom',
  311. 'foo',
  312. '>',
  313. 'databases/localhost/foo',
  314. ),
  315. shell=True,
  316. extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  317. run_to_completion=False,
  318. ).and_return(process).once()
  319. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
  320. def test_dump_data_sources_runs_pg_dump_with_directory_format():
  321. databases = [{'name': 'foo', 'format': 'directory'}]
  322. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  323. flexmock(module).should_receive('make_dump_path').and_return('')
  324. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  325. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  326. 'databases/localhost/foo'
  327. )
  328. flexmock(module.os.path).should_receive('exists').and_return(False)
  329. flexmock(module.dump).should_receive('create_parent_directory_for_dump')
  330. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  331. flexmock(module).should_receive('execute_command').with_args(
  332. (
  333. 'pg_dump',
  334. '--no-password',
  335. '--clean',
  336. '--if-exists',
  337. '--format',
  338. 'directory',
  339. '--file',
  340. 'databases/localhost/foo',
  341. 'foo',
  342. ),
  343. shell=True,
  344. extra_environment={'PGSSLMODE': 'disable'},
  345. ).and_return(flexmock()).once()
  346. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
  347. def test_dump_data_sources_runs_pg_dump_with_options():
  348. databases = [{'name': 'foo', 'options': '--stuff=such'}]
  349. process = flexmock()
  350. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  351. flexmock(module).should_receive('make_dump_path').and_return('')
  352. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  353. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  354. 'databases/localhost/foo'
  355. )
  356. flexmock(module.os.path).should_receive('exists').and_return(False)
  357. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  358. flexmock(module).should_receive('execute_command').with_args(
  359. (
  360. 'pg_dump',
  361. '--no-password',
  362. '--clean',
  363. '--if-exists',
  364. '--format',
  365. 'custom',
  366. '--stuff=such',
  367. 'foo',
  368. '>',
  369. 'databases/localhost/foo',
  370. ),
  371. shell=True,
  372. extra_environment={'PGSSLMODE': 'disable'},
  373. run_to_completion=False,
  374. ).and_return(process).once()
  375. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
  376. def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
  377. databases = [{'name': 'all'}]
  378. process = flexmock()
  379. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  380. flexmock(module).should_receive('make_dump_path').and_return('')
  381. flexmock(module).should_receive('database_names_to_dump').and_return(('all',))
  382. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  383. 'databases/localhost/all'
  384. )
  385. flexmock(module.os.path).should_receive('exists').and_return(False)
  386. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  387. flexmock(module).should_receive('execute_command').with_args(
  388. ('pg_dumpall', '--no-password', '--clean', '--if-exists', '>', 'databases/localhost/all'),
  389. shell=True,
  390. extra_environment={'PGSSLMODE': 'disable'},
  391. run_to_completion=False,
  392. ).and_return(process).once()
  393. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
  394. def test_dump_data_sources_runs_non_default_pg_dump():
  395. databases = [{'name': 'foo', 'pg_dump_command': 'special_pg_dump --compress *'}]
  396. process = flexmock()
  397. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  398. flexmock(module).should_receive('make_dump_path').and_return('')
  399. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  400. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  401. 'databases/localhost/foo'
  402. )
  403. flexmock(module.os.path).should_receive('exists').and_return(False)
  404. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  405. flexmock(module).should_receive('execute_command').with_args(
  406. (
  407. 'special_pg_dump',
  408. '--compress',
  409. "'*'", # Should get shell escaped to prevent injection attacks.
  410. '--no-password',
  411. '--clean',
  412. '--if-exists',
  413. '--format',
  414. 'custom',
  415. 'foo',
  416. '>',
  417. 'databases/localhost/foo',
  418. ),
  419. shell=True,
  420. extra_environment={'PGSSLMODE': 'disable'},
  421. run_to_completion=False,
  422. ).and_return(process).once()
  423. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
  424. def test_restore_data_source_dump_runs_pg_restore():
  425. hook_config = [{'name': 'foo', 'schemas': None}, {'name': 'bar'}]
  426. extract_process = flexmock(stdout=flexmock())
  427. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  428. flexmock(module).should_receive('make_dump_path')
  429. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  430. flexmock(module).should_receive('execute_command_with_processes').with_args(
  431. (
  432. 'pg_restore',
  433. '--no-password',
  434. '--if-exists',
  435. '--exit-on-error',
  436. '--clean',
  437. '--dbname',
  438. 'foo',
  439. ),
  440. processes=[extract_process],
  441. output_log_level=logging.DEBUG,
  442. input_file=extract_process.stdout,
  443. extra_environment={'PGSSLMODE': 'disable'},
  444. ).once()
  445. flexmock(module).should_receive('execute_command').with_args(
  446. (
  447. 'psql',
  448. '--no-password',
  449. '--no-psqlrc',
  450. '--quiet',
  451. '--dbname',
  452. 'foo',
  453. '--command',
  454. 'ANALYZE',
  455. ),
  456. extra_environment={'PGSSLMODE': 'disable'},
  457. ).once()
  458. module.restore_data_source_dump(
  459. hook_config,
  460. {},
  461. 'test.yaml',
  462. data_source={'name': 'foo'},
  463. dry_run=False,
  464. extract_process=extract_process,
  465. connection_params={
  466. 'hostname': None,
  467. 'port': None,
  468. 'username': None,
  469. 'password': None,
  470. },
  471. )
  472. def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
  473. hook_config = [
  474. {'name': 'foo', 'hostname': 'database.example.org', 'port': 5433, 'schemas': None}
  475. ]
  476. extract_process = flexmock(stdout=flexmock())
  477. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  478. flexmock(module).should_receive('make_dump_path')
  479. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  480. flexmock(module).should_receive('execute_command_with_processes').with_args(
  481. (
  482. 'pg_restore',
  483. '--no-password',
  484. '--if-exists',
  485. '--exit-on-error',
  486. '--clean',
  487. '--dbname',
  488. 'foo',
  489. '--host',
  490. 'database.example.org',
  491. '--port',
  492. '5433',
  493. ),
  494. processes=[extract_process],
  495. output_log_level=logging.DEBUG,
  496. input_file=extract_process.stdout,
  497. extra_environment={'PGSSLMODE': 'disable'},
  498. ).once()
  499. flexmock(module).should_receive('execute_command').with_args(
  500. (
  501. 'psql',
  502. '--no-password',
  503. '--no-psqlrc',
  504. '--quiet',
  505. '--host',
  506. 'database.example.org',
  507. '--port',
  508. '5433',
  509. '--dbname',
  510. 'foo',
  511. '--command',
  512. 'ANALYZE',
  513. ),
  514. extra_environment={'PGSSLMODE': 'disable'},
  515. ).once()
  516. module.restore_data_source_dump(
  517. hook_config,
  518. {},
  519. 'test.yaml',
  520. data_source=hook_config[0],
  521. dry_run=False,
  522. extract_process=extract_process,
  523. connection_params={
  524. 'hostname': None,
  525. 'port': None,
  526. 'username': None,
  527. 'password': None,
  528. },
  529. )
  530. def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
  531. hook_config = [
  532. {'name': 'foo', 'username': 'postgres', 'password': 'trustsome1', 'schemas': None}
  533. ]
  534. extract_process = flexmock(stdout=flexmock())
  535. flexmock(module).should_receive('make_extra_environment').and_return(
  536. {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
  537. )
  538. flexmock(module).should_receive('make_dump_path')
  539. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  540. flexmock(module).should_receive('execute_command_with_processes').with_args(
  541. (
  542. 'pg_restore',
  543. '--no-password',
  544. '--if-exists',
  545. '--exit-on-error',
  546. '--clean',
  547. '--dbname',
  548. 'foo',
  549. '--username',
  550. 'postgres',
  551. ),
  552. processes=[extract_process],
  553. output_log_level=logging.DEBUG,
  554. input_file=extract_process.stdout,
  555. extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  556. ).once()
  557. flexmock(module).should_receive('execute_command').with_args(
  558. (
  559. 'psql',
  560. '--no-password',
  561. '--no-psqlrc',
  562. '--quiet',
  563. '--username',
  564. 'postgres',
  565. '--dbname',
  566. 'foo',
  567. '--command',
  568. 'ANALYZE',
  569. ),
  570. extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  571. ).once()
  572. module.restore_data_source_dump(
  573. hook_config,
  574. {},
  575. 'test.yaml',
  576. data_source=hook_config[0],
  577. dry_run=False,
  578. extract_process=extract_process,
  579. connection_params={
  580. 'hostname': None,
  581. 'port': None,
  582. 'username': None,
  583. 'password': None,
  584. },
  585. )
  586. def test_restore_data_source_dump_with_connection_params_uses_connection_params_for_restore():
  587. hook_config = [
  588. {
  589. 'name': 'foo',
  590. 'hostname': 'database.example.org',
  591. 'port': 5433,
  592. 'username': 'postgres',
  593. 'password': 'trustsome1',
  594. 'restore_hostname': 'restorehost',
  595. 'restore_port': 'restoreport',
  596. 'restore_username': 'restoreusername',
  597. 'restore_password': 'restorepassword',
  598. 'schemas': None,
  599. }
  600. ]
  601. extract_process = flexmock(stdout=flexmock())
  602. flexmock(module).should_receive('make_extra_environment').and_return(
  603. {'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'}
  604. )
  605. flexmock(module).should_receive('make_dump_path')
  606. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  607. flexmock(module).should_receive('execute_command_with_processes').with_args(
  608. (
  609. 'pg_restore',
  610. '--no-password',
  611. '--if-exists',
  612. '--exit-on-error',
  613. '--clean',
  614. '--dbname',
  615. 'foo',
  616. '--host',
  617. 'clihost',
  618. '--port',
  619. 'cliport',
  620. '--username',
  621. 'cliusername',
  622. ),
  623. processes=[extract_process],
  624. output_log_level=logging.DEBUG,
  625. input_file=extract_process.stdout,
  626. extra_environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
  627. ).once()
  628. flexmock(module).should_receive('execute_command').with_args(
  629. (
  630. 'psql',
  631. '--no-password',
  632. '--no-psqlrc',
  633. '--quiet',
  634. '--host',
  635. 'clihost',
  636. '--port',
  637. 'cliport',
  638. '--username',
  639. 'cliusername',
  640. '--dbname',
  641. 'foo',
  642. '--command',
  643. 'ANALYZE',
  644. ),
  645. extra_environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
  646. ).once()
  647. module.restore_data_source_dump(
  648. hook_config,
  649. {},
  650. 'test.yaml',
  651. data_source={'name': 'foo'},
  652. dry_run=False,
  653. extract_process=extract_process,
  654. connection_params={
  655. 'hostname': 'clihost',
  656. 'port': 'cliport',
  657. 'username': 'cliusername',
  658. 'password': 'clipassword',
  659. },
  660. )
  661. def test_restore_data_source_dump_without_connection_params_uses_restore_params_in_config_for_restore():
  662. hook_config = [
  663. {
  664. 'name': 'foo',
  665. 'hostname': 'database.example.org',
  666. 'port': 5433,
  667. 'username': 'postgres',
  668. 'password': 'trustsome1',
  669. 'schemas': None,
  670. 'restore_hostname': 'restorehost',
  671. 'restore_port': 'restoreport',
  672. 'restore_username': 'restoreusername',
  673. 'restore_password': 'restorepassword',
  674. }
  675. ]
  676. extract_process = flexmock(stdout=flexmock())
  677. flexmock(module).should_receive('make_extra_environment').and_return(
  678. {'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'}
  679. )
  680. flexmock(module).should_receive('make_dump_path')
  681. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  682. flexmock(module).should_receive('execute_command_with_processes').with_args(
  683. (
  684. 'pg_restore',
  685. '--no-password',
  686. '--if-exists',
  687. '--exit-on-error',
  688. '--clean',
  689. '--dbname',
  690. 'foo',
  691. '--host',
  692. 'restorehost',
  693. '--port',
  694. 'restoreport',
  695. '--username',
  696. 'restoreusername',
  697. ),
  698. processes=[extract_process],
  699. output_log_level=logging.DEBUG,
  700. input_file=extract_process.stdout,
  701. extra_environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
  702. ).once()
  703. flexmock(module).should_receive('execute_command').with_args(
  704. (
  705. 'psql',
  706. '--no-password',
  707. '--no-psqlrc',
  708. '--quiet',
  709. '--host',
  710. 'restorehost',
  711. '--port',
  712. 'restoreport',
  713. '--username',
  714. 'restoreusername',
  715. '--dbname',
  716. 'foo',
  717. '--command',
  718. 'ANALYZE',
  719. ),
  720. extra_environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
  721. ).once()
  722. module.restore_data_source_dump(
  723. hook_config,
  724. {},
  725. 'test.yaml',
  726. data_source=hook_config[0],
  727. dry_run=False,
  728. extract_process=extract_process,
  729. connection_params={
  730. 'hostname': None,
  731. 'port': None,
  732. 'username': None,
  733. 'password': None,
  734. },
  735. )
  736. def test_restore_data_source_dump_runs_pg_restore_with_options():
  737. hook_config = [
  738. {
  739. 'name': 'foo',
  740. 'restore_options': '--harder',
  741. 'analyze_options': '--smarter',
  742. 'schemas': None,
  743. }
  744. ]
  745. extract_process = flexmock(stdout=flexmock())
  746. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  747. flexmock(module).should_receive('make_dump_path')
  748. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  749. flexmock(module).should_receive('execute_command_with_processes').with_args(
  750. (
  751. 'pg_restore',
  752. '--no-password',
  753. '--if-exists',
  754. '--exit-on-error',
  755. '--clean',
  756. '--dbname',
  757. 'foo',
  758. '--harder',
  759. ),
  760. processes=[extract_process],
  761. output_log_level=logging.DEBUG,
  762. input_file=extract_process.stdout,
  763. extra_environment={'PGSSLMODE': 'disable'},
  764. ).once()
  765. flexmock(module).should_receive('execute_command').with_args(
  766. (
  767. 'psql',
  768. '--no-password',
  769. '--no-psqlrc',
  770. '--quiet',
  771. '--dbname',
  772. 'foo',
  773. '--smarter',
  774. '--command',
  775. 'ANALYZE',
  776. ),
  777. extra_environment={'PGSSLMODE': 'disable'},
  778. ).once()
  779. module.restore_data_source_dump(
  780. hook_config,
  781. {},
  782. 'test.yaml',
  783. data_source=hook_config[0],
  784. dry_run=False,
  785. extract_process=extract_process,
  786. connection_params={
  787. 'hostname': None,
  788. 'port': None,
  789. 'username': None,
  790. 'password': None,
  791. },
  792. )
  793. def test_restore_data_source_dump_runs_psql_for_all_database_dump():
  794. hook_config = [{'name': 'all', 'schemas': None}]
  795. extract_process = flexmock(stdout=flexmock())
  796. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  797. flexmock(module).should_receive('make_dump_path')
  798. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  799. flexmock(module).should_receive('execute_command_with_processes').with_args(
  800. (
  801. 'psql',
  802. '--no-password',
  803. '--no-psqlrc',
  804. ),
  805. processes=[extract_process],
  806. output_log_level=logging.DEBUG,
  807. input_file=extract_process.stdout,
  808. extra_environment={'PGSSLMODE': 'disable'},
  809. ).once()
  810. flexmock(module).should_receive('execute_command').with_args(
  811. ('psql', '--no-password', '--no-psqlrc', '--quiet', '--command', 'ANALYZE'),
  812. extra_environment={'PGSSLMODE': 'disable'},
  813. ).once()
  814. module.restore_data_source_dump(
  815. hook_config,
  816. {},
  817. 'test.yaml',
  818. data_source={'name': 'all'},
  819. dry_run=False,
  820. extract_process=extract_process,
  821. connection_params={
  822. 'hostname': None,
  823. 'port': None,
  824. 'username': None,
  825. 'password': None,
  826. },
  827. )
  828. def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
  829. hook_config = [{'name': 'foo', 'format': 'plain', 'schemas': None}]
  830. extract_process = flexmock(stdout=flexmock())
  831. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  832. flexmock(module).should_receive('make_dump_path')
  833. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  834. flexmock(module).should_receive('execute_command_with_processes').with_args(
  835. ('psql', '--no-password', '--no-psqlrc', '--dbname', 'foo'),
  836. processes=[extract_process],
  837. output_log_level=logging.DEBUG,
  838. input_file=extract_process.stdout,
  839. extra_environment={'PGSSLMODE': 'disable'},
  840. ).once()
  841. flexmock(module).should_receive('execute_command').with_args(
  842. (
  843. 'psql',
  844. '--no-password',
  845. '--no-psqlrc',
  846. '--quiet',
  847. '--dbname',
  848. 'foo',
  849. '--command',
  850. 'ANALYZE',
  851. ),
  852. extra_environment={'PGSSLMODE': 'disable'},
  853. ).once()
  854. module.restore_data_source_dump(
  855. hook_config,
  856. {},
  857. 'test.yaml',
  858. data_source=hook_config[0],
  859. dry_run=False,
  860. extract_process=extract_process,
  861. connection_params={
  862. 'hostname': None,
  863. 'port': None,
  864. 'username': None,
  865. 'password': None,
  866. },
  867. )
  868. def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
  869. hook_config = [
  870. {
  871. 'name': 'foo',
  872. 'pg_restore_command': 'docker exec --workdir * mycontainer pg_restore',
  873. 'psql_command': 'docker exec --workdir * mycontainer psql',
  874. 'schemas': None,
  875. }
  876. ]
  877. extract_process = flexmock(stdout=flexmock())
  878. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  879. flexmock(module).should_receive('make_dump_path')
  880. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  881. flexmock(module).should_receive('execute_command_with_processes').with_args(
  882. (
  883. 'docker',
  884. 'exec',
  885. '--workdir',
  886. "'*'", # Should get shell escaped to prevent injection attacks.
  887. 'mycontainer',
  888. 'pg_restore',
  889. '--no-password',
  890. '--if-exists',
  891. '--exit-on-error',
  892. '--clean',
  893. '--dbname',
  894. 'foo',
  895. ),
  896. processes=[extract_process],
  897. output_log_level=logging.DEBUG,
  898. input_file=extract_process.stdout,
  899. extra_environment={'PGSSLMODE': 'disable'},
  900. ).once()
  901. flexmock(module).should_receive('execute_command').with_args(
  902. (
  903. 'docker',
  904. 'exec',
  905. '--workdir',
  906. "'*'", # Should get shell escaped to prevent injection attacks.
  907. 'mycontainer',
  908. 'psql',
  909. '--no-password',
  910. '--no-psqlrc',
  911. '--quiet',
  912. '--dbname',
  913. 'foo',
  914. '--command',
  915. 'ANALYZE',
  916. ),
  917. extra_environment={'PGSSLMODE': 'disable'},
  918. ).once()
  919. module.restore_data_source_dump(
  920. hook_config,
  921. {},
  922. 'test.yaml',
  923. data_source=hook_config[0],
  924. dry_run=False,
  925. extract_process=extract_process,
  926. connection_params={
  927. 'hostname': None,
  928. 'port': None,
  929. 'username': None,
  930. 'password': None,
  931. },
  932. )
  933. def test_restore_data_source_dump_with_dry_run_skips_restore():
  934. hook_config = [{'name': 'foo', 'schemas': None}]
  935. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  936. flexmock(module).should_receive('make_dump_path')
  937. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  938. flexmock(module).should_receive('execute_command_with_processes').never()
  939. module.restore_data_source_dump(
  940. hook_config,
  941. {},
  942. 'test.yaml',
  943. data_source={'name': 'foo'},
  944. dry_run=True,
  945. extract_process=flexmock(),
  946. connection_params={
  947. 'hostname': None,
  948. 'port': None,
  949. 'username': None,
  950. 'password': None,
  951. },
  952. )
  953. def test_restore_data_source_dump_without_extract_process_restores_from_disk():
  954. hook_config = [{'name': 'foo', 'schemas': None}]
  955. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  956. flexmock(module).should_receive('make_dump_path')
  957. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
  958. flexmock(module).should_receive('execute_command_with_processes').with_args(
  959. (
  960. 'pg_restore',
  961. '--no-password',
  962. '--if-exists',
  963. '--exit-on-error',
  964. '--clean',
  965. '--dbname',
  966. 'foo',
  967. '/dump/path',
  968. ),
  969. processes=[],
  970. output_log_level=logging.DEBUG,
  971. input_file=None,
  972. extra_environment={'PGSSLMODE': 'disable'},
  973. ).once()
  974. flexmock(module).should_receive('execute_command').with_args(
  975. (
  976. 'psql',
  977. '--no-password',
  978. '--no-psqlrc',
  979. '--quiet',
  980. '--dbname',
  981. 'foo',
  982. '--command',
  983. 'ANALYZE',
  984. ),
  985. extra_environment={'PGSSLMODE': 'disable'},
  986. ).once()
  987. module.restore_data_source_dump(
  988. hook_config,
  989. {},
  990. 'test.yaml',
  991. data_source={'name': 'foo'},
  992. dry_run=False,
  993. extract_process=None,
  994. connection_params={
  995. 'hostname': None,
  996. 'port': None,
  997. 'username': None,
  998. 'password': None,
  999. },
  1000. )
  1001. def test_restore_data_source_dump_with_schemas_restores_schemas():
  1002. hook_config = [{'name': 'foo', 'schemas': ['bar', 'baz']}]
  1003. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  1004. flexmock(module).should_receive('make_dump_path')
  1005. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
  1006. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1007. (
  1008. 'pg_restore',
  1009. '--no-password',
  1010. '--if-exists',
  1011. '--exit-on-error',
  1012. '--clean',
  1013. '--dbname',
  1014. 'foo',
  1015. '/dump/path',
  1016. '--schema',
  1017. 'bar',
  1018. '--schema',
  1019. 'baz',
  1020. ),
  1021. processes=[],
  1022. output_log_level=logging.DEBUG,
  1023. input_file=None,
  1024. extra_environment={'PGSSLMODE': 'disable'},
  1025. ).once()
  1026. flexmock(module).should_receive('execute_command').with_args(
  1027. (
  1028. 'psql',
  1029. '--no-password',
  1030. '--no-psqlrc',
  1031. '--quiet',
  1032. '--dbname',
  1033. 'foo',
  1034. '--command',
  1035. 'ANALYZE',
  1036. ),
  1037. extra_environment={'PGSSLMODE': 'disable'},
  1038. ).once()
  1039. module.restore_data_source_dump(
  1040. hook_config,
  1041. {},
  1042. 'test.yaml',
  1043. data_source=hook_config[0],
  1044. dry_run=False,
  1045. extract_process=None,
  1046. connection_params={
  1047. 'hostname': None,
  1048. 'port': None,
  1049. 'username': None,
  1050. 'password': None,
  1051. },
  1052. )