test_mongodb.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. import logging
  2. from flexmock import flexmock
  3. from borgmatic.hooks.data_source import mongodb as module
  4. def test_use_streaming_true_for_any_non_directory_format_databases():
  5. assert module.use_streaming(
  6. databases=[{'format': 'stuff'}, {'format': 'directory'}, {}],
  7. config=flexmock(),
  8. )
  9. def test_use_streaming_false_for_all_directory_format_databases():
  10. assert not module.use_streaming(
  11. databases=[{'format': 'directory'}, {'format': 'directory'}],
  12. config=flexmock(),
  13. )
  14. def test_use_streaming_false_for_no_databases():
  15. assert not module.use_streaming(databases=[], config=flexmock())
  16. def test_dump_data_sources_runs_mongodump_for_each_database():
  17. databases = [{'name': 'foo'}, {'name': 'bar'}]
  18. processes = [flexmock(), flexmock()]
  19. flexmock(module).should_receive('make_dump_path').and_return('')
  20. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  21. 'databases/localhost/foo'
  22. ).and_return('databases/localhost/bar')
  23. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  24. for name, process in zip(('foo', 'bar'), processes):
  25. flexmock(module).should_receive('execute_command').with_args(
  26. ('mongodump', '--db', name, '--archive', '>', f'databases/localhost/{name}'),
  27. shell=True,
  28. run_to_completion=False,
  29. ).and_return(process).once()
  30. assert (
  31. module.dump_data_sources(
  32. databases,
  33. {},
  34. config_paths=('test.yaml',),
  35. borgmatic_runtime_directory='/run/borgmatic',
  36. patterns=[],
  37. dry_run=False,
  38. )
  39. == processes
  40. )
  41. def test_dump_data_sources_with_dry_run_skips_mongodump():
  42. databases = [{'name': 'foo'}, {'name': 'bar'}]
  43. flexmock(module).should_receive('make_dump_path').and_return('')
  44. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  45. 'databases/localhost/foo'
  46. ).and_return('databases/localhost/bar')
  47. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  48. flexmock(module).should_receive('execute_command').never()
  49. assert (
  50. module.dump_data_sources(
  51. databases,
  52. {},
  53. config_paths=('test.yaml',),
  54. borgmatic_runtime_directory='/run/borgmatic',
  55. patterns=[],
  56. dry_run=True,
  57. )
  58. == []
  59. )
  60. def test_dump_data_sources_runs_mongodump_with_hostname_and_port():
  61. databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
  62. process = flexmock()
  63. flexmock(module).should_receive('make_dump_path').and_return('')
  64. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  65. 'databases/database.example.org/foo'
  66. )
  67. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  68. flexmock(module).should_receive('execute_command').with_args(
  69. (
  70. 'mongodump',
  71. '--host',
  72. 'database.example.org',
  73. '--port',
  74. '5433',
  75. '--db',
  76. 'foo',
  77. '--archive',
  78. '>',
  79. 'databases/database.example.org/foo',
  80. ),
  81. shell=True,
  82. run_to_completion=False,
  83. ).and_return(process).once()
  84. assert module.dump_data_sources(
  85. databases,
  86. {},
  87. config_paths=('test.yaml',),
  88. borgmatic_runtime_directory='/run/borgmatic',
  89. patterns=[],
  90. dry_run=False,
  91. ) == [process]
  92. def test_dump_data_sources_runs_mongodump_with_username_and_password():
  93. databases = [
  94. {
  95. 'name': 'foo',
  96. 'username': 'mongo',
  97. 'password': 'trustsome1',
  98. 'authentication_database': 'admin',
  99. }
  100. ]
  101. process = flexmock()
  102. flexmock(module).should_receive('make_dump_path').and_return('')
  103. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  104. 'databases/localhost/foo'
  105. )
  106. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  107. flexmock(module).should_receive('execute_command').with_args(
  108. (
  109. 'mongodump',
  110. '--username',
  111. 'mongo',
  112. '--password',
  113. 'trustsome1',
  114. '--authenticationDatabase',
  115. 'admin',
  116. '--db',
  117. 'foo',
  118. '--archive',
  119. '>',
  120. 'databases/localhost/foo',
  121. ),
  122. shell=True,
  123. run_to_completion=False,
  124. ).and_return(process).once()
  125. assert module.dump_data_sources(
  126. databases,
  127. {},
  128. config_paths=('test.yaml',),
  129. borgmatic_runtime_directory='/run/borgmatic',
  130. patterns=[],
  131. dry_run=False,
  132. ) == [process]
  133. def test_dump_data_sources_runs_mongodump_with_directory_format():
  134. databases = [{'name': 'foo', 'format': 'directory'}]
  135. flexmock(module).should_receive('make_dump_path').and_return('')
  136. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  137. 'databases/localhost/foo'
  138. )
  139. flexmock(module.dump).should_receive('create_parent_directory_for_dump')
  140. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  141. flexmock(module).should_receive('execute_command').with_args(
  142. ('mongodump', '--out', 'databases/localhost/foo', '--db', 'foo'),
  143. shell=True,
  144. ).and_return(flexmock()).once()
  145. assert (
  146. module.dump_data_sources(
  147. databases,
  148. {},
  149. config_paths=('test.yaml',),
  150. borgmatic_runtime_directory='/run/borgmatic',
  151. patterns=[],
  152. dry_run=False,
  153. )
  154. == []
  155. )
  156. def test_dump_data_sources_runs_mongodump_with_options():
  157. databases = [{'name': 'foo', 'options': '--stuff=such'}]
  158. process = flexmock()
  159. flexmock(module).should_receive('make_dump_path').and_return('')
  160. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  161. 'databases/localhost/foo'
  162. )
  163. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  164. flexmock(module).should_receive('execute_command').with_args(
  165. (
  166. 'mongodump',
  167. '--db',
  168. 'foo',
  169. '--stuff=such',
  170. '--archive',
  171. '>',
  172. 'databases/localhost/foo',
  173. ),
  174. shell=True,
  175. run_to_completion=False,
  176. ).and_return(process).once()
  177. assert module.dump_data_sources(
  178. databases,
  179. {},
  180. config_paths=('test.yaml',),
  181. borgmatic_runtime_directory='/run/borgmatic',
  182. patterns=[],
  183. dry_run=False,
  184. ) == [process]
  185. def test_dump_data_sources_runs_mongodumpall_for_all_databases():
  186. databases = [{'name': 'all'}]
  187. process = flexmock()
  188. flexmock(module).should_receive('make_dump_path').and_return('')
  189. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  190. 'databases/localhost/all'
  191. )
  192. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  193. flexmock(module).should_receive('execute_command').with_args(
  194. ('mongodump', '--archive', '>', 'databases/localhost/all'),
  195. shell=True,
  196. run_to_completion=False,
  197. ).and_return(process).once()
  198. assert module.dump_data_sources(
  199. databases,
  200. {},
  201. config_paths=('test.yaml',),
  202. borgmatic_runtime_directory='/run/borgmatic',
  203. patterns=[],
  204. dry_run=False,
  205. ) == [process]
  206. def test_build_dump_command_with_username_injection_attack_gets_escaped():
  207. database = {'name': 'test', 'username': 'bob; naughty-command'}
  208. command = module.build_dump_command(database, dump_filename='test', dump_format='archive')
  209. assert "'bob; naughty-command'" in command
  210. def test_restore_data_source_dump_runs_mongorestore():
  211. hook_config = [{'name': 'foo', 'schemas': None}, {'name': 'bar'}]
  212. extract_process = flexmock(stdout=flexmock())
  213. flexmock(module).should_receive('make_dump_path')
  214. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  215. flexmock(module).should_receive('execute_command_with_processes').with_args(
  216. ['mongorestore', '--archive', '--drop'],
  217. processes=[extract_process],
  218. output_log_level=logging.DEBUG,
  219. input_file=extract_process.stdout,
  220. ).once()
  221. module.restore_data_source_dump(
  222. hook_config,
  223. {},
  224. data_source={'name': 'foo'},
  225. dry_run=False,
  226. extract_process=extract_process,
  227. connection_params={
  228. 'hostname': None,
  229. 'port': None,
  230. 'username': None,
  231. 'password': None,
  232. },
  233. borgmatic_runtime_directory='/run/borgmatic',
  234. )
  235. def test_restore_data_source_dump_runs_mongorestore_with_hostname_and_port():
  236. hook_config = [
  237. {'name': 'foo', 'hostname': 'database.example.org', 'port': 5433, 'schemas': None}
  238. ]
  239. extract_process = flexmock(stdout=flexmock())
  240. flexmock(module).should_receive('make_dump_path')
  241. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  242. flexmock(module).should_receive('execute_command_with_processes').with_args(
  243. [
  244. 'mongorestore',
  245. '--archive',
  246. '--drop',
  247. '--host',
  248. 'database.example.org',
  249. '--port',
  250. '5433',
  251. ],
  252. processes=[extract_process],
  253. output_log_level=logging.DEBUG,
  254. input_file=extract_process.stdout,
  255. ).once()
  256. module.restore_data_source_dump(
  257. hook_config,
  258. {},
  259. data_source=hook_config[0],
  260. dry_run=False,
  261. extract_process=extract_process,
  262. connection_params={
  263. 'hostname': None,
  264. 'port': None,
  265. 'username': None,
  266. 'password': None,
  267. },
  268. borgmatic_runtime_directory='/run/borgmatic',
  269. )
  270. def test_restore_data_source_dump_runs_mongorestore_with_username_and_password():
  271. hook_config = [
  272. {
  273. 'name': 'foo',
  274. 'username': 'mongo',
  275. 'password': 'trustsome1',
  276. 'authentication_database': 'admin',
  277. 'schemas': None,
  278. }
  279. ]
  280. extract_process = flexmock(stdout=flexmock())
  281. flexmock(module).should_receive('make_dump_path')
  282. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  283. flexmock(module).should_receive('execute_command_with_processes').with_args(
  284. [
  285. 'mongorestore',
  286. '--archive',
  287. '--drop',
  288. '--username',
  289. 'mongo',
  290. '--password',
  291. 'trustsome1',
  292. '--authenticationDatabase',
  293. 'admin',
  294. ],
  295. processes=[extract_process],
  296. output_log_level=logging.DEBUG,
  297. input_file=extract_process.stdout,
  298. ).once()
  299. module.restore_data_source_dump(
  300. hook_config,
  301. {},
  302. data_source=hook_config[0],
  303. dry_run=False,
  304. extract_process=extract_process,
  305. connection_params={
  306. 'hostname': None,
  307. 'port': None,
  308. 'username': None,
  309. 'password': None,
  310. },
  311. borgmatic_runtime_directory='/run/borgmatic',
  312. )
  313. def test_restore_data_source_dump_with_connection_params_uses_connection_params_for_restore():
  314. hook_config = [
  315. {
  316. 'name': 'foo',
  317. 'username': 'mongo',
  318. 'password': 'trustsome1',
  319. 'authentication_database': 'admin',
  320. 'restore_hostname': 'restorehost',
  321. 'restore_port': 'restoreport',
  322. 'restore_username': 'restoreusername',
  323. 'restore_password': 'restorepassword',
  324. 'schemas': None,
  325. }
  326. ]
  327. extract_process = flexmock(stdout=flexmock())
  328. flexmock(module).should_receive('make_dump_path')
  329. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  330. flexmock(module).should_receive('execute_command_with_processes').with_args(
  331. [
  332. 'mongorestore',
  333. '--archive',
  334. '--drop',
  335. '--host',
  336. 'clihost',
  337. '--port',
  338. 'cliport',
  339. '--username',
  340. 'cliusername',
  341. '--password',
  342. 'clipassword',
  343. '--authenticationDatabase',
  344. 'admin',
  345. ],
  346. processes=[extract_process],
  347. output_log_level=logging.DEBUG,
  348. input_file=extract_process.stdout,
  349. ).once()
  350. module.restore_data_source_dump(
  351. hook_config,
  352. {},
  353. data_source=hook_config[0],
  354. dry_run=False,
  355. extract_process=extract_process,
  356. connection_params={
  357. 'hostname': 'clihost',
  358. 'port': 'cliport',
  359. 'username': 'cliusername',
  360. 'password': 'clipassword',
  361. },
  362. borgmatic_runtime_directory='/run/borgmatic',
  363. )
  364. def test_restore_data_source_dump_without_connection_params_uses_restore_params_in_config_for_restore():
  365. hook_config = [
  366. {
  367. 'name': 'foo',
  368. 'username': 'mongo',
  369. 'password': 'trustsome1',
  370. 'authentication_database': 'admin',
  371. 'schemas': None,
  372. 'restore_hostname': 'restorehost',
  373. 'restore_port': 'restoreport',
  374. 'restore_username': 'restoreuser',
  375. 'restore_password': 'restorepass',
  376. }
  377. ]
  378. extract_process = flexmock(stdout=flexmock())
  379. flexmock(module).should_receive('make_dump_path')
  380. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  381. flexmock(module).should_receive('execute_command_with_processes').with_args(
  382. [
  383. 'mongorestore',
  384. '--archive',
  385. '--drop',
  386. '--host',
  387. 'restorehost',
  388. '--port',
  389. 'restoreport',
  390. '--username',
  391. 'restoreuser',
  392. '--password',
  393. 'restorepass',
  394. '--authenticationDatabase',
  395. 'admin',
  396. ],
  397. processes=[extract_process],
  398. output_log_level=logging.DEBUG,
  399. input_file=extract_process.stdout,
  400. ).once()
  401. module.restore_data_source_dump(
  402. hook_config,
  403. {},
  404. data_source=hook_config[0],
  405. dry_run=False,
  406. extract_process=extract_process,
  407. connection_params={
  408. 'hostname': None,
  409. 'port': None,
  410. 'username': None,
  411. 'password': None,
  412. },
  413. borgmatic_runtime_directory='/run/borgmatic',
  414. )
  415. def test_restore_data_source_dump_runs_mongorestore_with_options():
  416. hook_config = [{'name': 'foo', 'restore_options': '--harder', 'schemas': None}]
  417. extract_process = flexmock(stdout=flexmock())
  418. flexmock(module).should_receive('make_dump_path')
  419. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  420. flexmock(module).should_receive('execute_command_with_processes').with_args(
  421. ['mongorestore', '--archive', '--drop', '--harder'],
  422. processes=[extract_process],
  423. output_log_level=logging.DEBUG,
  424. input_file=extract_process.stdout,
  425. ).once()
  426. module.restore_data_source_dump(
  427. hook_config,
  428. {},
  429. data_source=hook_config[0],
  430. dry_run=False,
  431. extract_process=extract_process,
  432. connection_params={
  433. 'hostname': None,
  434. 'port': None,
  435. 'username': None,
  436. 'password': None,
  437. },
  438. borgmatic_runtime_directory='/run/borgmatic',
  439. )
  440. def test_restore_databases_dump_runs_mongorestore_with_schemas():
  441. hook_config = [{'name': 'foo', 'schemas': ['bar', 'baz']}]
  442. extract_process = flexmock(stdout=flexmock())
  443. flexmock(module).should_receive('make_dump_path')
  444. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  445. flexmock(module).should_receive('execute_command_with_processes').with_args(
  446. [
  447. 'mongorestore',
  448. '--archive',
  449. '--drop',
  450. '--nsInclude',
  451. 'bar',
  452. '--nsInclude',
  453. 'baz',
  454. ],
  455. processes=[extract_process],
  456. output_log_level=logging.DEBUG,
  457. input_file=extract_process.stdout,
  458. ).once()
  459. module.restore_data_source_dump(
  460. hook_config,
  461. {},
  462. data_source=hook_config[0],
  463. dry_run=False,
  464. extract_process=extract_process,
  465. connection_params={
  466. 'hostname': None,
  467. 'port': None,
  468. 'username': None,
  469. 'password': None,
  470. },
  471. borgmatic_runtime_directory='/run/borgmatic',
  472. )
  473. def test_restore_data_source_dump_runs_psql_for_all_database_dump():
  474. hook_config = [{'name': 'all', 'schemas': None}]
  475. extract_process = flexmock(stdout=flexmock())
  476. flexmock(module).should_receive('make_dump_path')
  477. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  478. flexmock(module).should_receive('execute_command_with_processes').with_args(
  479. ['mongorestore', '--archive'],
  480. processes=[extract_process],
  481. output_log_level=logging.DEBUG,
  482. input_file=extract_process.stdout,
  483. ).once()
  484. module.restore_data_source_dump(
  485. hook_config,
  486. {},
  487. data_source=hook_config[0],
  488. dry_run=False,
  489. extract_process=extract_process,
  490. connection_params={
  491. 'hostname': None,
  492. 'port': None,
  493. 'username': None,
  494. 'password': None,
  495. },
  496. borgmatic_runtime_directory='/run/borgmatic',
  497. )
  498. def test_restore_data_source_dump_with_dry_run_skips_restore():
  499. hook_config = [{'name': 'foo', 'schemas': None}]
  500. flexmock(module).should_receive('make_dump_path')
  501. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  502. flexmock(module).should_receive('execute_command_with_processes').never()
  503. module.restore_data_source_dump(
  504. hook_config,
  505. {},
  506. data_source={'name': 'foo'},
  507. dry_run=True,
  508. extract_process=flexmock(),
  509. connection_params={
  510. 'hostname': None,
  511. 'port': None,
  512. 'username': None,
  513. 'password': None,
  514. },
  515. borgmatic_runtime_directory='/run/borgmatic',
  516. )
  517. def test_restore_data_source_dump_without_extract_process_restores_from_disk():
  518. hook_config = [{'name': 'foo', 'format': 'directory', 'schemas': None}]
  519. flexmock(module).should_receive('make_dump_path')
  520. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
  521. flexmock(module).should_receive('execute_command_with_processes').with_args(
  522. ['mongorestore', '--dir', '/dump/path', '--drop'],
  523. processes=[],
  524. output_log_level=logging.DEBUG,
  525. input_file=None,
  526. ).once()
  527. module.restore_data_source_dump(
  528. hook_config,
  529. {},
  530. data_source={'name': 'foo'},
  531. dry_run=False,
  532. extract_process=None,
  533. connection_params={
  534. 'hostname': None,
  535. 'port': None,
  536. 'username': None,
  537. 'password': None,
  538. },
  539. borgmatic_runtime_directory='/run/borgmatic',
  540. )