test_postgresql.py 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136
  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_runs_pg_dump_with_directory_format():
  279. databases = [{'name': 'foo', 'format': 'directory'}]
  280. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  281. flexmock(module).should_receive('make_dump_path').and_return('')
  282. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  283. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  284. 'databases/localhost/foo'
  285. )
  286. flexmock(module.os.path).should_receive('exists').and_return(False)
  287. flexmock(module.dump).should_receive('create_parent_directory_for_dump')
  288. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  289. flexmock(module).should_receive('execute_command').with_args(
  290. (
  291. 'pg_dump',
  292. '--no-password',
  293. '--clean',
  294. '--if-exists',
  295. '--format',
  296. 'directory',
  297. '--file',
  298. 'databases/localhost/foo',
  299. 'foo',
  300. ),
  301. shell=True,
  302. extra_environment={'PGSSLMODE': 'disable'},
  303. ).and_return(flexmock()).once()
  304. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
  305. def test_dump_data_sources_runs_pg_dump_with_options():
  306. databases = [{'name': 'foo', 'options': '--stuff=such'}]
  307. process = flexmock()
  308. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  309. flexmock(module).should_receive('make_dump_path').and_return('')
  310. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  311. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  312. 'databases/localhost/foo'
  313. )
  314. flexmock(module.os.path).should_receive('exists').and_return(False)
  315. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  316. flexmock(module).should_receive('execute_command').with_args(
  317. (
  318. 'pg_dump',
  319. '--no-password',
  320. '--clean',
  321. '--if-exists',
  322. '--format',
  323. 'custom',
  324. '--stuff=such',
  325. 'foo',
  326. '>',
  327. 'databases/localhost/foo',
  328. ),
  329. shell=True,
  330. extra_environment={'PGSSLMODE': 'disable'},
  331. run_to_completion=False,
  332. ).and_return(process).once()
  333. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
  334. def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
  335. databases = [{'name': 'all'}]
  336. process = flexmock()
  337. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  338. flexmock(module).should_receive('make_dump_path').and_return('')
  339. flexmock(module).should_receive('database_names_to_dump').and_return(('all',))
  340. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  341. 'databases/localhost/all'
  342. )
  343. flexmock(module.os.path).should_receive('exists').and_return(False)
  344. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  345. flexmock(module).should_receive('execute_command').with_args(
  346. ('pg_dumpall', '--no-password', '--clean', '--if-exists', '>', 'databases/localhost/all'),
  347. shell=True,
  348. extra_environment={'PGSSLMODE': 'disable'},
  349. run_to_completion=False,
  350. ).and_return(process).once()
  351. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
  352. def test_dump_data_sources_runs_non_default_pg_dump():
  353. databases = [{'name': 'foo', 'pg_dump_command': 'special_pg_dump'}]
  354. process = flexmock()
  355. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  356. flexmock(module).should_receive('make_dump_path').and_return('')
  357. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
  358. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  359. 'databases/localhost/foo'
  360. )
  361. flexmock(module.os.path).should_receive('exists').and_return(False)
  362. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  363. flexmock(module).should_receive('execute_command').with_args(
  364. (
  365. 'special_pg_dump',
  366. '--no-password',
  367. '--clean',
  368. '--if-exists',
  369. '--format',
  370. 'custom',
  371. 'foo',
  372. '>',
  373. 'databases/localhost/foo',
  374. ),
  375. shell=True,
  376. extra_environment={'PGSSLMODE': 'disable'},
  377. run_to_completion=False,
  378. ).and_return(process).once()
  379. assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
  380. def test_restore_data_source_dump_runs_pg_restore():
  381. hook_config = [{'name': 'foo', 'schemas': None}, {'name': 'bar'}]
  382. extract_process = flexmock(stdout=flexmock())
  383. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  384. flexmock(module).should_receive('make_dump_path')
  385. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  386. flexmock(module).should_receive('execute_command_with_processes').with_args(
  387. (
  388. 'pg_restore',
  389. '--no-password',
  390. '--if-exists',
  391. '--exit-on-error',
  392. '--clean',
  393. '--dbname',
  394. 'foo',
  395. ),
  396. processes=[extract_process],
  397. output_log_level=logging.DEBUG,
  398. input_file=extract_process.stdout,
  399. extra_environment={'PGSSLMODE': 'disable'},
  400. ).once()
  401. flexmock(module).should_receive('execute_command').with_args(
  402. (
  403. 'psql',
  404. '--no-password',
  405. '--no-psqlrc',
  406. '--quiet',
  407. '--dbname',
  408. 'foo',
  409. '--command',
  410. 'ANALYZE',
  411. ),
  412. extra_environment={'PGSSLMODE': 'disable'},
  413. ).once()
  414. module.restore_data_source_dump(
  415. hook_config,
  416. {},
  417. 'test.yaml',
  418. data_source={'name': 'foo'},
  419. dry_run=False,
  420. extract_process=extract_process,
  421. connection_params={
  422. 'hostname': None,
  423. 'port': None,
  424. 'username': None,
  425. 'password': None,
  426. },
  427. )
  428. def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
  429. hook_config = [
  430. {'name': 'foo', 'hostname': 'database.example.org', 'port': 5433, 'schemas': None}
  431. ]
  432. extract_process = flexmock(stdout=flexmock())
  433. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  434. flexmock(module).should_receive('make_dump_path')
  435. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  436. flexmock(module).should_receive('execute_command_with_processes').with_args(
  437. (
  438. 'pg_restore',
  439. '--no-password',
  440. '--if-exists',
  441. '--exit-on-error',
  442. '--clean',
  443. '--dbname',
  444. 'foo',
  445. '--host',
  446. 'database.example.org',
  447. '--port',
  448. '5433',
  449. ),
  450. processes=[extract_process],
  451. output_log_level=logging.DEBUG,
  452. input_file=extract_process.stdout,
  453. extra_environment={'PGSSLMODE': 'disable'},
  454. ).once()
  455. flexmock(module).should_receive('execute_command').with_args(
  456. (
  457. 'psql',
  458. '--no-password',
  459. '--no-psqlrc',
  460. '--quiet',
  461. '--host',
  462. 'database.example.org',
  463. '--port',
  464. '5433',
  465. '--dbname',
  466. 'foo',
  467. '--command',
  468. 'ANALYZE',
  469. ),
  470. extra_environment={'PGSSLMODE': 'disable'},
  471. ).once()
  472. module.restore_data_source_dump(
  473. hook_config,
  474. {},
  475. 'test.yaml',
  476. data_source=hook_config[0],
  477. dry_run=False,
  478. extract_process=extract_process,
  479. connection_params={
  480. 'hostname': None,
  481. 'port': None,
  482. 'username': None,
  483. 'password': None,
  484. },
  485. )
  486. def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
  487. hook_config = [
  488. {'name': 'foo', 'username': 'postgres', 'password': 'trustsome1', 'schemas': None}
  489. ]
  490. extract_process = flexmock(stdout=flexmock())
  491. flexmock(module).should_receive('make_extra_environment').and_return(
  492. {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
  493. )
  494. flexmock(module).should_receive('make_dump_path')
  495. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  496. flexmock(module).should_receive('execute_command_with_processes').with_args(
  497. (
  498. 'pg_restore',
  499. '--no-password',
  500. '--if-exists',
  501. '--exit-on-error',
  502. '--clean',
  503. '--dbname',
  504. 'foo',
  505. '--username',
  506. 'postgres',
  507. ),
  508. processes=[extract_process],
  509. output_log_level=logging.DEBUG,
  510. input_file=extract_process.stdout,
  511. extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  512. ).once()
  513. flexmock(module).should_receive('execute_command').with_args(
  514. (
  515. 'psql',
  516. '--no-password',
  517. '--no-psqlrc',
  518. '--quiet',
  519. '--username',
  520. 'postgres',
  521. '--dbname',
  522. 'foo',
  523. '--command',
  524. 'ANALYZE',
  525. ),
  526. extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
  527. ).once()
  528. module.restore_data_source_dump(
  529. hook_config,
  530. {},
  531. 'test.yaml',
  532. data_source=hook_config[0],
  533. dry_run=False,
  534. extract_process=extract_process,
  535. connection_params={
  536. 'hostname': None,
  537. 'port': None,
  538. 'username': None,
  539. 'password': None,
  540. },
  541. )
  542. def test_restore_data_source_dump_with_connection_params_uses_connection_params_for_restore():
  543. hook_config = [
  544. {
  545. 'name': 'foo',
  546. 'hostname': 'database.example.org',
  547. 'port': 5433,
  548. 'username': 'postgres',
  549. 'password': 'trustsome1',
  550. 'restore_hostname': 'restorehost',
  551. 'restore_port': 'restoreport',
  552. 'restore_username': 'restoreusername',
  553. 'restore_password': 'restorepassword',
  554. 'schemas': None,
  555. }
  556. ]
  557. extract_process = flexmock(stdout=flexmock())
  558. flexmock(module).should_receive('make_extra_environment').and_return(
  559. {'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'}
  560. )
  561. flexmock(module).should_receive('make_dump_path')
  562. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  563. flexmock(module).should_receive('execute_command_with_processes').with_args(
  564. (
  565. 'pg_restore',
  566. '--no-password',
  567. '--if-exists',
  568. '--exit-on-error',
  569. '--clean',
  570. '--dbname',
  571. 'foo',
  572. '--host',
  573. 'clihost',
  574. '--port',
  575. 'cliport',
  576. '--username',
  577. 'cliusername',
  578. ),
  579. processes=[extract_process],
  580. output_log_level=logging.DEBUG,
  581. input_file=extract_process.stdout,
  582. extra_environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
  583. ).once()
  584. flexmock(module).should_receive('execute_command').with_args(
  585. (
  586. 'psql',
  587. '--no-password',
  588. '--no-psqlrc',
  589. '--quiet',
  590. '--host',
  591. 'clihost',
  592. '--port',
  593. 'cliport',
  594. '--username',
  595. 'cliusername',
  596. '--dbname',
  597. 'foo',
  598. '--command',
  599. 'ANALYZE',
  600. ),
  601. extra_environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
  602. ).once()
  603. module.restore_data_source_dump(
  604. hook_config,
  605. {},
  606. 'test.yaml',
  607. data_source={'name': 'foo'},
  608. dry_run=False,
  609. extract_process=extract_process,
  610. connection_params={
  611. 'hostname': 'clihost',
  612. 'port': 'cliport',
  613. 'username': 'cliusername',
  614. 'password': 'clipassword',
  615. },
  616. )
  617. def test_restore_data_source_dump_without_connection_params_uses_restore_params_in_config_for_restore():
  618. hook_config = [
  619. {
  620. 'name': 'foo',
  621. 'hostname': 'database.example.org',
  622. 'port': 5433,
  623. 'username': 'postgres',
  624. 'password': 'trustsome1',
  625. 'schemas': None,
  626. 'restore_hostname': 'restorehost',
  627. 'restore_port': 'restoreport',
  628. 'restore_username': 'restoreusername',
  629. 'restore_password': 'restorepassword',
  630. }
  631. ]
  632. extract_process = flexmock(stdout=flexmock())
  633. flexmock(module).should_receive('make_extra_environment').and_return(
  634. {'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'}
  635. )
  636. flexmock(module).should_receive('make_dump_path')
  637. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  638. flexmock(module).should_receive('execute_command_with_processes').with_args(
  639. (
  640. 'pg_restore',
  641. '--no-password',
  642. '--if-exists',
  643. '--exit-on-error',
  644. '--clean',
  645. '--dbname',
  646. 'foo',
  647. '--host',
  648. 'restorehost',
  649. '--port',
  650. 'restoreport',
  651. '--username',
  652. 'restoreusername',
  653. ),
  654. processes=[extract_process],
  655. output_log_level=logging.DEBUG,
  656. input_file=extract_process.stdout,
  657. extra_environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
  658. ).once()
  659. flexmock(module).should_receive('execute_command').with_args(
  660. (
  661. 'psql',
  662. '--no-password',
  663. '--no-psqlrc',
  664. '--quiet',
  665. '--host',
  666. 'restorehost',
  667. '--port',
  668. 'restoreport',
  669. '--username',
  670. 'restoreusername',
  671. '--dbname',
  672. 'foo',
  673. '--command',
  674. 'ANALYZE',
  675. ),
  676. extra_environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
  677. ).once()
  678. module.restore_data_source_dump(
  679. hook_config,
  680. {},
  681. 'test.yaml',
  682. data_source=hook_config[0],
  683. dry_run=False,
  684. extract_process=extract_process,
  685. connection_params={
  686. 'hostname': None,
  687. 'port': None,
  688. 'username': None,
  689. 'password': None,
  690. },
  691. )
  692. def test_restore_data_source_dump_runs_pg_restore_with_options():
  693. hook_config = [
  694. {
  695. 'name': 'foo',
  696. 'restore_options': '--harder',
  697. 'analyze_options': '--smarter',
  698. 'schemas': None,
  699. }
  700. ]
  701. extract_process = flexmock(stdout=flexmock())
  702. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  703. flexmock(module).should_receive('make_dump_path')
  704. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  705. flexmock(module).should_receive('execute_command_with_processes').with_args(
  706. (
  707. 'pg_restore',
  708. '--no-password',
  709. '--if-exists',
  710. '--exit-on-error',
  711. '--clean',
  712. '--dbname',
  713. 'foo',
  714. '--harder',
  715. ),
  716. processes=[extract_process],
  717. output_log_level=logging.DEBUG,
  718. input_file=extract_process.stdout,
  719. extra_environment={'PGSSLMODE': 'disable'},
  720. ).once()
  721. flexmock(module).should_receive('execute_command').with_args(
  722. (
  723. 'psql',
  724. '--no-password',
  725. '--no-psqlrc',
  726. '--quiet',
  727. '--dbname',
  728. 'foo',
  729. '--smarter',
  730. '--command',
  731. 'ANALYZE',
  732. ),
  733. extra_environment={'PGSSLMODE': 'disable'},
  734. ).once()
  735. module.restore_data_source_dump(
  736. hook_config,
  737. {},
  738. 'test.yaml',
  739. data_source=hook_config[0],
  740. dry_run=False,
  741. extract_process=extract_process,
  742. connection_params={
  743. 'hostname': None,
  744. 'port': None,
  745. 'username': None,
  746. 'password': None,
  747. },
  748. )
  749. def test_restore_data_source_dump_runs_psql_for_all_database_dump():
  750. hook_config = [{'name': 'all', 'schemas': None}]
  751. extract_process = flexmock(stdout=flexmock())
  752. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  753. flexmock(module).should_receive('make_dump_path')
  754. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  755. flexmock(module).should_receive('execute_command_with_processes').with_args(
  756. (
  757. 'psql',
  758. '--no-password',
  759. '--no-psqlrc',
  760. ),
  761. processes=[extract_process],
  762. output_log_level=logging.DEBUG,
  763. input_file=extract_process.stdout,
  764. extra_environment={'PGSSLMODE': 'disable'},
  765. ).once()
  766. flexmock(module).should_receive('execute_command').with_args(
  767. ('psql', '--no-password', '--no-psqlrc', '--quiet', '--command', 'ANALYZE'),
  768. extra_environment={'PGSSLMODE': 'disable'},
  769. ).once()
  770. module.restore_data_source_dump(
  771. hook_config,
  772. {},
  773. 'test.yaml',
  774. data_source={'name': 'all'},
  775. dry_run=False,
  776. extract_process=extract_process,
  777. connection_params={
  778. 'hostname': None,
  779. 'port': None,
  780. 'username': None,
  781. 'password': None,
  782. },
  783. )
  784. def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
  785. hook_config = [{'name': 'foo', 'format': 'plain', 'schemas': None}]
  786. extract_process = flexmock(stdout=flexmock())
  787. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  788. flexmock(module).should_receive('make_dump_path')
  789. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  790. flexmock(module).should_receive('execute_command_with_processes').with_args(
  791. ('psql', '--no-password', '--no-psqlrc', '--dbname', 'foo'),
  792. processes=[extract_process],
  793. output_log_level=logging.DEBUG,
  794. input_file=extract_process.stdout,
  795. extra_environment={'PGSSLMODE': 'disable'},
  796. ).once()
  797. flexmock(module).should_receive('execute_command').with_args(
  798. (
  799. 'psql',
  800. '--no-password',
  801. '--no-psqlrc',
  802. '--quiet',
  803. '--dbname',
  804. 'foo',
  805. '--command',
  806. 'ANALYZE',
  807. ),
  808. extra_environment={'PGSSLMODE': 'disable'},
  809. ).once()
  810. module.restore_data_source_dump(
  811. hook_config,
  812. {},
  813. 'test.yaml',
  814. data_source=hook_config[0],
  815. dry_run=False,
  816. extract_process=extract_process,
  817. connection_params={
  818. 'hostname': None,
  819. 'port': None,
  820. 'username': None,
  821. 'password': None,
  822. },
  823. )
  824. def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
  825. hook_config = [
  826. {
  827. 'name': 'foo',
  828. 'pg_restore_command': 'docker exec mycontainer pg_restore',
  829. 'psql_command': 'docker exec mycontainer psql',
  830. 'schemas': None,
  831. }
  832. ]
  833. extract_process = flexmock(stdout=flexmock())
  834. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  835. flexmock(module).should_receive('make_dump_path')
  836. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  837. flexmock(module).should_receive('execute_command_with_processes').with_args(
  838. (
  839. 'docker',
  840. 'exec',
  841. 'mycontainer',
  842. 'pg_restore',
  843. '--no-password',
  844. '--if-exists',
  845. '--exit-on-error',
  846. '--clean',
  847. '--dbname',
  848. 'foo',
  849. ),
  850. processes=[extract_process],
  851. output_log_level=logging.DEBUG,
  852. input_file=extract_process.stdout,
  853. extra_environment={'PGSSLMODE': 'disable'},
  854. ).once()
  855. flexmock(module).should_receive('execute_command').with_args(
  856. (
  857. 'docker',
  858. 'exec',
  859. 'mycontainer',
  860. 'psql',
  861. '--no-password',
  862. '--no-psqlrc',
  863. '--quiet',
  864. '--dbname',
  865. 'foo',
  866. '--command',
  867. 'ANALYZE',
  868. ),
  869. extra_environment={'PGSSLMODE': 'disable'},
  870. ).once()
  871. module.restore_data_source_dump(
  872. hook_config,
  873. {},
  874. 'test.yaml',
  875. data_source=hook_config[0],
  876. dry_run=False,
  877. extract_process=extract_process,
  878. connection_params={
  879. 'hostname': None,
  880. 'port': None,
  881. 'username': None,
  882. 'password': None,
  883. },
  884. )
  885. def test_restore_data_source_dump_with_dry_run_skips_restore():
  886. hook_config = [{'name': 'foo', 'schemas': None}]
  887. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  888. flexmock(module).should_receive('make_dump_path')
  889. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  890. flexmock(module).should_receive('execute_command_with_processes').never()
  891. module.restore_data_source_dump(
  892. hook_config,
  893. {},
  894. 'test.yaml',
  895. data_source={'name': 'foo'},
  896. dry_run=True,
  897. extract_process=flexmock(),
  898. connection_params={
  899. 'hostname': None,
  900. 'port': None,
  901. 'username': None,
  902. 'password': None,
  903. },
  904. )
  905. def test_restore_data_source_dump_without_extract_process_restores_from_disk():
  906. hook_config = [{'name': 'foo', 'schemas': None}]
  907. flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
  908. flexmock(module).should_receive('make_dump_path')
  909. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
  910. flexmock(module).should_receive('execute_command_with_processes').with_args(
  911. (
  912. 'pg_restore',
  913. '--no-password',
  914. '--if-exists',
  915. '--exit-on-error',
  916. '--clean',
  917. '--dbname',
  918. 'foo',
  919. '/dump/path',
  920. ),
  921. processes=[],
  922. output_log_level=logging.DEBUG,
  923. input_file=None,
  924. extra_environment={'PGSSLMODE': 'disable'},
  925. ).once()
  926. flexmock(module).should_receive('execute_command').with_args(
  927. (
  928. 'psql',
  929. '--no-password',
  930. '--no-psqlrc',
  931. '--quiet',
  932. '--dbname',
  933. 'foo',
  934. '--command',
  935. 'ANALYZE',
  936. ),
  937. extra_environment={'PGSSLMODE': 'disable'},
  938. ).once()
  939. module.restore_data_source_dump(
  940. hook_config,
  941. {},
  942. 'test.yaml',
  943. data_source={'name': 'foo'},
  944. dry_run=False,
  945. extract_process=None,
  946. connection_params={
  947. 'hostname': None,
  948. 'port': None,
  949. 'username': None,
  950. 'password': None,
  951. },
  952. )
  953. def test_restore_data_source_dump_with_schemas_restores_schemas():
  954. hook_config = [{'name': 'foo', 'schemas': ['bar', 'baz']}]
  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. '--schema',
  969. 'bar',
  970. '--schema',
  971. 'baz',
  972. ),
  973. processes=[],
  974. output_log_level=logging.DEBUG,
  975. input_file=None,
  976. extra_environment={'PGSSLMODE': 'disable'},
  977. ).once()
  978. flexmock(module).should_receive('execute_command').with_args(
  979. (
  980. 'psql',
  981. '--no-password',
  982. '--no-psqlrc',
  983. '--quiet',
  984. '--dbname',
  985. 'foo',
  986. '--command',
  987. 'ANALYZE',
  988. ),
  989. extra_environment={'PGSSLMODE': 'disable'},
  990. ).once()
  991. module.restore_data_source_dump(
  992. hook_config,
  993. {},
  994. 'test.yaml',
  995. data_source=hook_config[0],
  996. dry_run=False,
  997. extract_process=None,
  998. connection_params={
  999. 'hostname': None,
  1000. 'port': None,
  1001. 'username': None,
  1002. 'password': None,
  1003. },
  1004. )