test_postgresql.py 52 KB

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