test_postgresql.py 38 KB

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