test_mariadb.py 56 KB

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