test_mysql.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248
  1. import logging
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.hooks.data_source import mysql as module
  5. def test_database_names_to_dump_passes_through_name():
  6. environment = flexmock()
  7. names = module.database_names_to_dump(
  8. {'name': 'foo'}, {}, 'root', 'trustsome1', environment, dry_run=False
  9. )
  10. assert names == ('foo',)
  11. def test_database_names_to_dump_bails_for_dry_run():
  12. environment = flexmock()
  13. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  14. 'resolve_credential'
  15. ).replace_with(lambda value, config: value)
  16. flexmock(module).should_receive('execute_command_and_capture_output').never()
  17. names = module.database_names_to_dump(
  18. {'name': 'all'}, {}, 'root', 'trustsome1', environment, dry_run=True
  19. )
  20. assert names == ()
  21. def test_database_names_to_dump_queries_mysql_for_database_names():
  22. environment = flexmock()
  23. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  24. 'resolve_credential'
  25. ).replace_with(lambda value, config: value)
  26. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  27. 'parse_extra_options'
  28. ).and_return((), None)
  29. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  30. 'make_defaults_file_options'
  31. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  32. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  33. (
  34. 'mysql',
  35. '--defaults-extra-file=/dev/fd/99',
  36. '--skip-column-names',
  37. '--batch',
  38. '--execute',
  39. 'show schemas',
  40. ),
  41. environment=environment,
  42. ).and_return('foo\nbar\nmysql\n').once()
  43. names = module.database_names_to_dump(
  44. {'name': 'all'}, {}, 'root', 'trustsome1', environment, dry_run=False
  45. )
  46. assert names == ('foo', 'bar')
  47. def test_database_names_to_dump_runs_mysql_with_tls():
  48. environment = flexmock()
  49. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  50. 'resolve_credential'
  51. ).replace_with(lambda value, config: value)
  52. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  53. 'parse_extra_options'
  54. ).and_return((), None)
  55. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  56. 'make_defaults_file_options'
  57. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  58. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  59. (
  60. 'mysql',
  61. '--defaults-extra-file=/dev/fd/99',
  62. '--ssl',
  63. '--skip-column-names',
  64. '--batch',
  65. '--execute',
  66. 'show schemas',
  67. ),
  68. environment=environment,
  69. ).and_return('foo\nbar\nmysql\n').once()
  70. names = module.database_names_to_dump(
  71. {'name': 'all', 'tls': True}, {}, 'root', 'trustsome1', environment, dry_run=False
  72. )
  73. assert names == ('foo', 'bar')
  74. def test_database_names_to_dump_runs_mysql_without_tls():
  75. environment = flexmock()
  76. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  77. 'resolve_credential'
  78. ).replace_with(lambda value, config: value)
  79. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  80. 'parse_extra_options'
  81. ).and_return((), None)
  82. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  83. 'make_defaults_file_options'
  84. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  85. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  86. (
  87. 'mysql',
  88. '--defaults-extra-file=/dev/fd/99',
  89. '--skip-ssl',
  90. '--skip-column-names',
  91. '--batch',
  92. '--execute',
  93. 'show schemas',
  94. ),
  95. environment=environment,
  96. ).and_return('foo\nbar\nmysql\n').once()
  97. names = module.database_names_to_dump(
  98. {'name': 'all', 'tls': False}, {}, 'root', 'trustsome1', environment, dry_run=False
  99. )
  100. assert names == ('foo', 'bar')
  101. def test_use_streaming_true_for_any_databases():
  102. assert module.use_streaming(
  103. databases=[flexmock(), flexmock()],
  104. config=flexmock(),
  105. )
  106. def test_use_streaming_false_for_no_databases():
  107. assert not module.use_streaming(databases=[], config=flexmock())
  108. def test_dump_data_sources_dumps_each_database():
  109. databases = [{'name': 'foo'}, {'name': 'bar'}]
  110. processes = [flexmock(), flexmock()]
  111. flexmock(module).should_receive('make_dump_path').and_return('')
  112. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  113. 'resolve_credential'
  114. ).and_return(None)
  115. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  116. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  117. ('bar',)
  118. )
  119. for name, process in zip(('foo', 'bar'), processes):
  120. flexmock(module).should_receive('execute_dump_command').with_args(
  121. database={'name': name},
  122. config={},
  123. username=None,
  124. password=None,
  125. dump_path=object,
  126. database_names=(name,),
  127. environment={'USER': 'root'},
  128. dry_run=object,
  129. dry_run_label=object,
  130. ).and_return(process).once()
  131. assert (
  132. module.dump_data_sources(
  133. databases,
  134. {},
  135. config_paths=('test.yaml',),
  136. borgmatic_runtime_directory='/run/borgmatic',
  137. patterns=[],
  138. dry_run=False,
  139. )
  140. == processes
  141. )
  142. def test_dump_data_sources_dumps_with_password():
  143. database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
  144. process = flexmock()
  145. flexmock(module).should_receive('make_dump_path').and_return('')
  146. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  147. 'resolve_credential'
  148. ).replace_with(lambda value, config: value)
  149. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  150. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  151. ('bar',)
  152. )
  153. flexmock(module).should_receive('execute_dump_command').with_args(
  154. database=database,
  155. config={},
  156. username='root',
  157. password='trustsome1',
  158. dump_path=object,
  159. database_names=('foo',),
  160. environment={'USER': 'root'},
  161. dry_run=object,
  162. dry_run_label=object,
  163. ).and_return(process).once()
  164. assert module.dump_data_sources(
  165. [database],
  166. {},
  167. config_paths=('test.yaml',),
  168. borgmatic_runtime_directory='/run/borgmatic',
  169. patterns=[],
  170. dry_run=False,
  171. ) == [process]
  172. def test_dump_data_sources_dumps_all_databases_at_once():
  173. databases = [{'name': 'all'}]
  174. process = flexmock()
  175. flexmock(module).should_receive('make_dump_path').and_return('')
  176. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  177. 'resolve_credential'
  178. ).and_return(None)
  179. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  180. flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
  181. flexmock(module).should_receive('execute_dump_command').with_args(
  182. database={'name': 'all'},
  183. config={},
  184. username=None,
  185. password=None,
  186. dump_path=object,
  187. database_names=('foo', 'bar'),
  188. environment={'USER': 'root'},
  189. dry_run=object,
  190. dry_run_label=object,
  191. ).and_return(process).once()
  192. assert module.dump_data_sources(
  193. databases,
  194. {},
  195. config_paths=('test.yaml',),
  196. borgmatic_runtime_directory='/run/borgmatic',
  197. patterns=[],
  198. dry_run=False,
  199. ) == [process]
  200. def test_dump_data_sources_dumps_all_databases_separately_when_format_configured():
  201. databases = [{'name': 'all', 'format': 'sql'}]
  202. processes = [flexmock(), flexmock()]
  203. flexmock(module).should_receive('make_dump_path').and_return('')
  204. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  205. 'resolve_credential'
  206. ).and_return(None)
  207. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  208. flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
  209. for name, process in zip(('foo', 'bar'), processes):
  210. flexmock(module).should_receive('execute_dump_command').with_args(
  211. database={'name': name, 'format': 'sql'},
  212. config={},
  213. username=None,
  214. password=None,
  215. dump_path=object,
  216. database_names=(name,),
  217. environment={'USER': 'root'},
  218. dry_run=object,
  219. dry_run_label=object,
  220. ).and_return(process).once()
  221. assert (
  222. module.dump_data_sources(
  223. databases,
  224. {},
  225. config_paths=('test.yaml',),
  226. borgmatic_runtime_directory='/run/borgmatic',
  227. patterns=[],
  228. dry_run=False,
  229. )
  230. == processes
  231. )
  232. def test_database_names_to_dump_runs_mysql_with_list_options():
  233. database = {'name': 'all', 'list_options': '--defaults-extra-file=my.cnf --skip-ssl'}
  234. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  235. 'parse_extra_options'
  236. ).and_return(('--skip-ssl',), 'my.cnf')
  237. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  238. 'make_defaults_file_options'
  239. ).with_args('root', 'trustsome1', 'my.cnf').and_return(('--defaults-extra-file=/dev/fd/99',))
  240. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  241. (
  242. 'mysql',
  243. '--defaults-extra-file=/dev/fd/99',
  244. '--skip-ssl',
  245. '--skip-column-names',
  246. '--batch',
  247. '--execute',
  248. 'show schemas',
  249. ),
  250. environment=None,
  251. ).and_return(('foo\nbar')).once()
  252. assert module.database_names_to_dump(database, {}, 'root', 'trustsome1', None, '') == (
  253. 'foo',
  254. 'bar',
  255. )
  256. def test_database_names_to_dump_runs_non_default_mysql_with_list_options():
  257. database = {
  258. 'name': 'all',
  259. 'list_options': '--defaults-extra-file=my.cnf --skip-ssl',
  260. 'mysql_command': 'custom_mysql',
  261. }
  262. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  263. 'parse_extra_options'
  264. ).and_return(('--skip-ssl',), 'my.cnf')
  265. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  266. 'make_defaults_file_options'
  267. ).with_args('root', 'trustsome1', 'my.cnf').and_return(('--defaults-extra-file=/dev/fd/99',))
  268. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  269. environment=None,
  270. full_command=(
  271. 'custom_mysql', # Custom MySQL command
  272. '--defaults-extra-file=/dev/fd/99',
  273. '--skip-ssl',
  274. '--skip-column-names',
  275. '--batch',
  276. '--execute',
  277. 'show schemas',
  278. ),
  279. ).and_return(('foo\nbar')).once()
  280. assert module.database_names_to_dump(database, {}, 'root', 'trustsome1', None, '') == (
  281. 'foo',
  282. 'bar',
  283. )
  284. def test_execute_dump_command_runs_mysqldump():
  285. process = flexmock()
  286. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  287. flexmock(module.os.path).should_receive('exists').and_return(False)
  288. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  289. 'resolve_credential'
  290. ).replace_with(lambda value, config: value)
  291. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  292. 'parse_extra_options'
  293. ).and_return((), None)
  294. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  295. 'make_defaults_file_options'
  296. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  297. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  298. flexmock(module).should_receive('execute_command').with_args(
  299. (
  300. 'mysqldump',
  301. '--defaults-extra-file=/dev/fd/99',
  302. '--add-drop-database',
  303. '--databases',
  304. 'foo',
  305. '--result-file',
  306. 'dump',
  307. ),
  308. environment=None,
  309. run_to_completion=False,
  310. ).and_return(process).once()
  311. assert (
  312. module.execute_dump_command(
  313. database={'name': 'foo'},
  314. config={},
  315. username='root',
  316. password='trustsome1',
  317. dump_path=flexmock(),
  318. database_names=('foo',),
  319. environment=None,
  320. dry_run=False,
  321. dry_run_label='',
  322. )
  323. == process
  324. )
  325. def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
  326. process = flexmock()
  327. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  328. flexmock(module.os.path).should_receive('exists').and_return(False)
  329. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  330. 'resolve_credential'
  331. ).replace_with(lambda value, config: value)
  332. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  333. 'parse_extra_options'
  334. ).and_return((), None)
  335. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  336. 'make_defaults_file_options'
  337. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  338. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  339. flexmock(module).should_receive('execute_command').with_args(
  340. (
  341. 'mysqldump',
  342. '--defaults-extra-file=/dev/fd/99',
  343. '--databases',
  344. 'foo',
  345. '--result-file',
  346. 'dump',
  347. ),
  348. environment=None,
  349. run_to_completion=False,
  350. ).and_return(process).once()
  351. assert (
  352. module.execute_dump_command(
  353. database={'name': 'foo', 'add_drop_database': False},
  354. config={},
  355. username='root',
  356. password='trustsome1',
  357. dump_path=flexmock(),
  358. database_names=('foo',),
  359. environment=None,
  360. dry_run=False,
  361. dry_run_label='',
  362. )
  363. == process
  364. )
  365. def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
  366. process = flexmock()
  367. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  368. flexmock(module.os.path).should_receive('exists').and_return(False)
  369. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  370. 'resolve_credential'
  371. ).replace_with(lambda value, config: value)
  372. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  373. 'parse_extra_options'
  374. ).and_return((), None)
  375. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  376. 'make_defaults_file_options'
  377. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  378. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  379. flexmock(module).should_receive('execute_command').with_args(
  380. (
  381. 'mysqldump',
  382. '--defaults-extra-file=/dev/fd/99',
  383. '--add-drop-database',
  384. '--host',
  385. 'database.example.org',
  386. '--port',
  387. '5433',
  388. '--protocol',
  389. 'tcp',
  390. '--databases',
  391. 'foo',
  392. '--result-file',
  393. 'dump',
  394. ),
  395. environment=None,
  396. run_to_completion=False,
  397. ).and_return(process).once()
  398. assert (
  399. module.execute_dump_command(
  400. database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
  401. config={},
  402. username='root',
  403. password='trustsome1',
  404. dump_path=flexmock(),
  405. database_names=('foo',),
  406. environment=None,
  407. dry_run=False,
  408. dry_run_label='',
  409. )
  410. == process
  411. )
  412. def test_execute_dump_command_runs_mysqldump_with_tls():
  413. process = flexmock()
  414. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  415. flexmock(module.os.path).should_receive('exists').and_return(False)
  416. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  417. 'resolve_credential'
  418. ).replace_with(lambda value, config: value)
  419. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  420. 'parse_extra_options'
  421. ).and_return((), None)
  422. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  423. 'make_defaults_file_options'
  424. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  425. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  426. flexmock(module).should_receive('execute_command').with_args(
  427. (
  428. 'mysqldump',
  429. '--defaults-extra-file=/dev/fd/99',
  430. '--add-drop-database',
  431. '--ssl',
  432. '--databases',
  433. 'foo',
  434. '--result-file',
  435. 'dump',
  436. ),
  437. environment=None,
  438. run_to_completion=False,
  439. ).and_return(process).once()
  440. assert (
  441. module.execute_dump_command(
  442. database={'name': 'foo', 'tls': True},
  443. config={},
  444. username='root',
  445. password='trustsome1',
  446. dump_path=flexmock(),
  447. database_names=('foo',),
  448. environment=None,
  449. dry_run=False,
  450. dry_run_label='',
  451. )
  452. == process
  453. )
  454. def test_execute_dump_command_runs_mysqldump_without_tls():
  455. process = flexmock()
  456. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  457. flexmock(module.os.path).should_receive('exists').and_return(False)
  458. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  459. 'resolve_credential'
  460. ).replace_with(lambda value, config: value)
  461. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  462. 'parse_extra_options'
  463. ).and_return((), None)
  464. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  465. 'make_defaults_file_options'
  466. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  467. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  468. flexmock(module).should_receive('execute_command').with_args(
  469. (
  470. 'mysqldump',
  471. '--defaults-extra-file=/dev/fd/99',
  472. '--add-drop-database',
  473. '--skip-ssl',
  474. '--databases',
  475. 'foo',
  476. '--result-file',
  477. 'dump',
  478. ),
  479. environment=None,
  480. run_to_completion=False,
  481. ).and_return(process).once()
  482. assert (
  483. module.execute_dump_command(
  484. database={'name': 'foo', 'tls': False},
  485. config={},
  486. username='root',
  487. password='trustsome1',
  488. dump_path=flexmock(),
  489. database_names=('foo',),
  490. environment=None,
  491. dry_run=False,
  492. dry_run_label='',
  493. )
  494. == process
  495. )
  496. def test_execute_dump_command_runs_mysqldump_with_username_and_password():
  497. process = flexmock()
  498. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  499. flexmock(module.os.path).should_receive('exists').and_return(False)
  500. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  501. 'resolve_credential'
  502. ).replace_with(lambda value, config: value)
  503. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  504. 'parse_extra_options'
  505. ).and_return((), None)
  506. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  507. 'make_defaults_file_options'
  508. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  509. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  510. flexmock(module).should_receive('execute_command').with_args(
  511. (
  512. 'mysqldump',
  513. '--defaults-extra-file=/dev/fd/99',
  514. '--add-drop-database',
  515. '--databases',
  516. 'foo',
  517. '--result-file',
  518. 'dump',
  519. ),
  520. environment={},
  521. run_to_completion=False,
  522. ).and_return(process).once()
  523. assert (
  524. module.execute_dump_command(
  525. database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
  526. config={},
  527. username='root',
  528. password='trustsome1',
  529. dump_path=flexmock(),
  530. database_names=('foo',),
  531. environment={},
  532. dry_run=False,
  533. dry_run_label='',
  534. )
  535. == process
  536. )
  537. def test_execute_dump_command_runs_mysqldump_with_options():
  538. process = flexmock()
  539. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  540. flexmock(module.os.path).should_receive('exists').and_return(False)
  541. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  542. 'resolve_credential'
  543. ).replace_with(lambda value, config: value)
  544. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  545. 'parse_extra_options'
  546. ).and_return(('--stuff=such',), None)
  547. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  548. 'make_defaults_file_options'
  549. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  550. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  551. flexmock(module).should_receive('execute_command').with_args(
  552. (
  553. 'mysqldump',
  554. '--defaults-extra-file=/dev/fd/99',
  555. '--stuff=such',
  556. '--add-drop-database',
  557. '--databases',
  558. 'foo',
  559. '--result-file',
  560. 'dump',
  561. ),
  562. environment=None,
  563. run_to_completion=False,
  564. ).and_return(process).once()
  565. assert (
  566. module.execute_dump_command(
  567. database={'name': 'foo', 'options': '--stuff=such'},
  568. config={},
  569. username='root',
  570. password='trustsome1',
  571. dump_path=flexmock(),
  572. database_names=('foo',),
  573. environment=None,
  574. dry_run=False,
  575. dry_run_label='',
  576. )
  577. == process
  578. )
  579. def test_execute_dump_command_runs_non_default_mysqldump():
  580. process = flexmock()
  581. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  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.borgmatic.hooks.data_source.mariadb).should_receive(
  587. 'parse_extra_options'
  588. ).and_return((), None)
  589. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  590. 'make_defaults_file_options'
  591. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  592. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  593. flexmock(module).should_receive('execute_command').with_args(
  594. (
  595. 'custom_mysqldump', # Custom MySQL dump command
  596. '--defaults-extra-file=/dev/fd/99',
  597. '--add-drop-database',
  598. '--databases',
  599. 'foo',
  600. '--result-file',
  601. 'dump',
  602. ),
  603. environment=None,
  604. run_to_completion=False,
  605. ).and_return(process).once()
  606. assert (
  607. module.execute_dump_command(
  608. database={
  609. 'name': 'foo',
  610. 'mysql_dump_command': 'custom_mysqldump',
  611. }, # Custom MySQL dump command specified
  612. config={},
  613. username='root',
  614. password='trustsome1',
  615. dump_path=flexmock(),
  616. database_names=('foo',),
  617. environment=None,
  618. dry_run=False,
  619. dry_run_label='',
  620. )
  621. == process
  622. )
  623. def test_execute_dump_command_with_duplicate_dump_skips_mysqldump():
  624. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  625. flexmock(module.os.path).should_receive('exists').and_return(True)
  626. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  627. 'parse_extra_options'
  628. ).and_return((), None)
  629. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  630. 'make_defaults_file_options'
  631. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  632. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  633. flexmock(module).should_receive('execute_command').never()
  634. assert (
  635. module.execute_dump_command(
  636. database={'name': 'foo'},
  637. config={},
  638. username='root',
  639. password='trustsome1',
  640. dump_path=flexmock(),
  641. database_names=('foo',),
  642. environment=None,
  643. dry_run=True,
  644. dry_run_label='SO DRY',
  645. )
  646. is None
  647. )
  648. def test_execute_dump_command_with_dry_run_skips_mysqldump():
  649. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  650. flexmock(module.os.path).should_receive('exists').and_return(False)
  651. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  652. 'resolve_credential'
  653. ).replace_with(lambda value, config: value)
  654. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  655. 'parse_extra_options'
  656. ).and_return((), None)
  657. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  658. 'make_defaults_file_options'
  659. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  660. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  661. flexmock(module).should_receive('execute_command').never()
  662. assert (
  663. module.execute_dump_command(
  664. database={'name': 'foo'},
  665. config={},
  666. username='root',
  667. password='trustsome1',
  668. dump_path=flexmock(),
  669. database_names=('foo',),
  670. environment=None,
  671. dry_run=True,
  672. dry_run_label='SO DRY',
  673. )
  674. is None
  675. )
  676. def test_dump_data_sources_errors_for_missing_all_databases():
  677. databases = [{'name': 'all'}]
  678. flexmock(module).should_receive('make_dump_path').and_return('')
  679. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  680. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  681. 'resolve_credential'
  682. ).replace_with(lambda value, config: value)
  683. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  684. 'databases/localhost/all'
  685. )
  686. flexmock(module).should_receive('database_names_to_dump').and_return(())
  687. with pytest.raises(ValueError):
  688. assert module.dump_data_sources(
  689. databases,
  690. {},
  691. config_paths=('test.yaml',),
  692. borgmatic_runtime_directory='/run/borgmatic',
  693. patterns=[],
  694. dry_run=False,
  695. )
  696. def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
  697. databases = [{'name': 'all'}]
  698. flexmock(module).should_receive('make_dump_path').and_return('')
  699. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  700. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  701. 'resolve_credential'
  702. ).replace_with(lambda value, config: value)
  703. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  704. 'databases/localhost/all'
  705. )
  706. flexmock(module).should_receive('database_names_to_dump').and_return(())
  707. assert (
  708. module.dump_data_sources(
  709. databases,
  710. {},
  711. config_paths=('test.yaml',),
  712. borgmatic_runtime_directory='/run/borgmatic',
  713. patterns=[],
  714. dry_run=True,
  715. )
  716. == []
  717. )
  718. def test_restore_data_source_dump_runs_mysql_to_restore():
  719. hook_config = [{'name': 'foo'}, {'name': 'bar'}]
  720. extract_process = flexmock(stdout=flexmock())
  721. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  722. 'resolve_credential'
  723. ).replace_with(lambda value, config: value)
  724. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  725. 'parse_extra_options'
  726. ).and_return((), None)
  727. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  728. 'make_defaults_file_options'
  729. ).with_args(None, None, None).and_return(())
  730. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  731. flexmock(module).should_receive('execute_command_with_processes').with_args(
  732. ('mysql', '--batch'),
  733. processes=[extract_process],
  734. output_log_level=logging.DEBUG,
  735. input_file=extract_process.stdout,
  736. environment={'USER': 'root'},
  737. ).once()
  738. module.restore_data_source_dump(
  739. hook_config,
  740. {},
  741. data_source={'name': 'foo'},
  742. dry_run=False,
  743. extract_process=extract_process,
  744. connection_params={
  745. 'hostname': None,
  746. 'port': None,
  747. 'username': None,
  748. 'password': None,
  749. },
  750. borgmatic_runtime_directory='/run/borgmatic',
  751. )
  752. def test_restore_data_source_dump_runs_mysql_with_options():
  753. hook_config = [{'name': 'foo', 'restore_options': '--harder'}]
  754. extract_process = flexmock(stdout=flexmock())
  755. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  756. 'resolve_credential'
  757. ).replace_with(lambda value, config: value)
  758. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  759. 'parse_extra_options'
  760. ).and_return(('--harder',), None)
  761. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  762. 'make_defaults_file_options'
  763. ).with_args(None, None, None).and_return(())
  764. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  765. flexmock(module).should_receive('execute_command_with_processes').with_args(
  766. ('mysql', '--harder', '--batch'),
  767. processes=[extract_process],
  768. output_log_level=logging.DEBUG,
  769. input_file=extract_process.stdout,
  770. environment={'USER': 'root'},
  771. ).once()
  772. module.restore_data_source_dump(
  773. hook_config,
  774. {},
  775. data_source=hook_config[0],
  776. dry_run=False,
  777. extract_process=extract_process,
  778. connection_params={
  779. 'hostname': None,
  780. 'port': None,
  781. 'username': None,
  782. 'password': None,
  783. },
  784. borgmatic_runtime_directory='/run/borgmatic',
  785. )
  786. def test_restore_data_source_dump_runs_non_default_mysql_with_options():
  787. hook_config = [{'name': 'foo', 'mysql_command': 'custom_mysql', 'restore_options': '--harder'}]
  788. extract_process = flexmock(stdout=flexmock())
  789. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  790. 'resolve_credential'
  791. ).replace_with(lambda value, config: value)
  792. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  793. 'parse_extra_options'
  794. ).and_return(('--harder',), None)
  795. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  796. 'make_defaults_file_options'
  797. ).with_args(None, None, None).and_return(())
  798. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  799. flexmock(module).should_receive('execute_command_with_processes').with_args(
  800. ('custom_mysql', '--harder', '--batch'),
  801. processes=[extract_process],
  802. output_log_level=logging.DEBUG,
  803. input_file=extract_process.stdout,
  804. environment={'USER': 'root'},
  805. ).once()
  806. module.restore_data_source_dump(
  807. hook_config,
  808. {},
  809. data_source=hook_config[0],
  810. dry_run=False,
  811. extract_process=extract_process,
  812. connection_params={
  813. 'hostname': None,
  814. 'port': None,
  815. 'username': None,
  816. 'password': None,
  817. },
  818. borgmatic_runtime_directory='/run/borgmatic',
  819. )
  820. def test_restore_data_source_dump_runs_mysql_with_hostname_and_port():
  821. hook_config = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
  822. extract_process = flexmock(stdout=flexmock())
  823. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  824. 'resolve_credential'
  825. ).replace_with(lambda value, config: value)
  826. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  827. 'parse_extra_options'
  828. ).and_return((), None)
  829. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  830. 'make_defaults_file_options'
  831. ).with_args(None, None, None).and_return(())
  832. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  833. flexmock(module).should_receive('execute_command_with_processes').with_args(
  834. (
  835. 'mysql',
  836. '--batch',
  837. '--host',
  838. 'database.example.org',
  839. '--port',
  840. '5433',
  841. '--protocol',
  842. 'tcp',
  843. ),
  844. processes=[extract_process],
  845. output_log_level=logging.DEBUG,
  846. input_file=extract_process.stdout,
  847. environment={'USER': 'root'},
  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_mysql_with_tls():
  864. hook_config = [{'name': 'foo', 'tls': True}]
  865. extract_process = flexmock(stdout=flexmock())
  866. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  867. 'resolve_credential'
  868. ).replace_with(lambda value, config: value)
  869. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  870. 'parse_extra_options'
  871. ).and_return((), None)
  872. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  873. 'make_defaults_file_options'
  874. ).with_args(None, None, None).and_return(())
  875. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  876. flexmock(module).should_receive('execute_command_with_processes').with_args(
  877. (
  878. 'mysql',
  879. '--batch',
  880. '--ssl',
  881. ),
  882. processes=[extract_process],
  883. output_log_level=logging.DEBUG,
  884. input_file=extract_process.stdout,
  885. environment={'USER': 'root'},
  886. ).once()
  887. module.restore_data_source_dump(
  888. hook_config,
  889. {},
  890. data_source=hook_config[0],
  891. dry_run=False,
  892. extract_process=extract_process,
  893. connection_params={
  894. 'hostname': None,
  895. 'port': None,
  896. 'username': None,
  897. 'password': None,
  898. },
  899. borgmatic_runtime_directory='/run/borgmatic',
  900. )
  901. def test_restore_data_source_dump_runs_mysql_without_tls():
  902. hook_config = [{'name': 'foo', 'tls': False}]
  903. extract_process = flexmock(stdout=flexmock())
  904. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  905. 'resolve_credential'
  906. ).replace_with(lambda value, config: value)
  907. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  908. 'parse_extra_options'
  909. ).and_return((), None)
  910. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  911. 'make_defaults_file_options'
  912. ).with_args(None, None, None).and_return(())
  913. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  914. flexmock(module).should_receive('execute_command_with_processes').with_args(
  915. (
  916. 'mysql',
  917. '--batch',
  918. '--skip-ssl',
  919. ),
  920. processes=[extract_process],
  921. output_log_level=logging.DEBUG,
  922. input_file=extract_process.stdout,
  923. environment={'USER': 'root'},
  924. ).once()
  925. module.restore_data_source_dump(
  926. hook_config,
  927. {},
  928. data_source=hook_config[0],
  929. dry_run=False,
  930. extract_process=extract_process,
  931. connection_params={
  932. 'hostname': None,
  933. 'port': None,
  934. 'username': None,
  935. 'password': None,
  936. },
  937. borgmatic_runtime_directory='/run/borgmatic',
  938. )
  939. def test_restore_data_source_dump_runs_mysql_with_username_and_password():
  940. hook_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
  941. extract_process = flexmock(stdout=flexmock())
  942. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  943. 'resolve_credential'
  944. ).replace_with(lambda value, config: value)
  945. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  946. 'parse_extra_options'
  947. ).and_return((), None)
  948. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  949. 'make_defaults_file_options'
  950. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  951. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  952. flexmock(module).should_receive('execute_command_with_processes').with_args(
  953. ('mysql', '--defaults-extra-file=/dev/fd/99', '--batch'),
  954. processes=[extract_process],
  955. output_log_level=logging.DEBUG,
  956. input_file=extract_process.stdout,
  957. environment={'USER': 'root'},
  958. ).once()
  959. module.restore_data_source_dump(
  960. hook_config,
  961. {},
  962. data_source=hook_config[0],
  963. dry_run=False,
  964. extract_process=extract_process,
  965. connection_params={
  966. 'hostname': None,
  967. 'port': None,
  968. 'username': None,
  969. 'password': None,
  970. },
  971. borgmatic_runtime_directory='/run/borgmatic',
  972. )
  973. def test_restore_data_source_dump_with_connection_params_uses_connection_params_for_restore():
  974. hook_config = [
  975. {
  976. 'name': 'foo',
  977. 'username': 'root',
  978. 'password': 'trustsome1',
  979. 'restore_hostname': 'restorehost',
  980. 'restore_port': 'restoreport',
  981. 'restore_username': 'restoreusername',
  982. 'restore_password': 'restorepassword',
  983. }
  984. ]
  985. extract_process = flexmock(stdout=flexmock())
  986. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  987. 'resolve_credential'
  988. ).replace_with(lambda value, config: value)
  989. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  990. 'parse_extra_options'
  991. ).and_return((), None)
  992. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  993. 'make_defaults_file_options'
  994. ).with_args('cliusername', 'clipassword', None).and_return(
  995. ('--defaults-extra-file=/dev/fd/99',)
  996. )
  997. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  998. flexmock(module).should_receive('execute_command_with_processes').with_args(
  999. (
  1000. 'mysql',
  1001. '--defaults-extra-file=/dev/fd/99',
  1002. '--batch',
  1003. '--host',
  1004. 'clihost',
  1005. '--port',
  1006. 'cliport',
  1007. '--protocol',
  1008. 'tcp',
  1009. ),
  1010. processes=[extract_process],
  1011. output_log_level=logging.DEBUG,
  1012. input_file=extract_process.stdout,
  1013. environment={'USER': 'root'},
  1014. ).once()
  1015. module.restore_data_source_dump(
  1016. hook_config,
  1017. {},
  1018. data_source={'name': 'foo'},
  1019. dry_run=False,
  1020. extract_process=extract_process,
  1021. connection_params={
  1022. 'hostname': 'clihost',
  1023. 'port': 'cliport',
  1024. 'username': 'cliusername',
  1025. 'password': 'clipassword',
  1026. },
  1027. borgmatic_runtime_directory='/run/borgmatic',
  1028. )
  1029. def test_restore_data_source_dump_without_connection_params_uses_restore_params_in_config_for_restore():
  1030. hook_config = [
  1031. {
  1032. 'name': 'foo',
  1033. 'username': 'root',
  1034. 'password': 'trustsome1',
  1035. 'hostname': 'dbhost',
  1036. 'port': 'dbport',
  1037. 'tls': True,
  1038. 'restore_username': 'restoreuser',
  1039. 'restore_password': 'restorepass',
  1040. 'restore_hostname': 'restorehost',
  1041. 'restore_port': 'restoreport',
  1042. 'restore_tls': False,
  1043. }
  1044. ]
  1045. extract_process = flexmock(stdout=flexmock())
  1046. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1047. 'resolve_credential'
  1048. ).replace_with(lambda value, config: value)
  1049. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1050. 'parse_extra_options'
  1051. ).and_return((), None)
  1052. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1053. 'make_defaults_file_options'
  1054. ).with_args('restoreuser', 'restorepass', None).and_return(
  1055. ('--defaults-extra-file=/dev/fd/99',)
  1056. )
  1057. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1058. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1059. (
  1060. 'mysql',
  1061. '--defaults-extra-file=/dev/fd/99',
  1062. '--batch',
  1063. '--host',
  1064. 'restorehost',
  1065. '--port',
  1066. 'restoreport',
  1067. '--protocol',
  1068. 'tcp',
  1069. '--skip-ssl',
  1070. ),
  1071. processes=[extract_process],
  1072. output_log_level=logging.DEBUG,
  1073. input_file=extract_process.stdout,
  1074. environment={'USER': 'root'},
  1075. ).once()
  1076. module.restore_data_source_dump(
  1077. hook_config,
  1078. {},
  1079. data_source=hook_config[0],
  1080. dry_run=False,
  1081. extract_process=extract_process,
  1082. connection_params={
  1083. 'hostname': None,
  1084. 'port': None,
  1085. 'username': None,
  1086. 'password': None,
  1087. },
  1088. borgmatic_runtime_directory='/run/borgmatic',
  1089. )
  1090. def test_restore_data_source_dump_with_dry_run_skips_restore():
  1091. hook_config = [{'name': 'foo'}]
  1092. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1093. 'resolve_credential'
  1094. ).replace_with(lambda value, config: value)
  1095. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1096. 'parse_extra_options'
  1097. ).and_return((), None)
  1098. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1099. 'make_defaults_file_options'
  1100. ).with_args(None, None, None).and_return(())
  1101. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1102. flexmock(module).should_receive('execute_command_with_processes').never()
  1103. module.restore_data_source_dump(
  1104. hook_config,
  1105. {},
  1106. data_source={'name': 'foo'},
  1107. dry_run=True,
  1108. extract_process=flexmock(),
  1109. connection_params={
  1110. 'hostname': None,
  1111. 'port': None,
  1112. 'username': None,
  1113. 'password': None,
  1114. },
  1115. borgmatic_runtime_directory='/run/borgmatic',
  1116. )