test_sqlite.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. import logging
  2. from flexmock import flexmock
  3. from borgmatic.hooks.data_source import sqlite as module
  4. def test_use_streaming_true_for_any_databases():
  5. assert module.use_streaming(
  6. databases=[flexmock(), flexmock()],
  7. config=flexmock(),
  8. )
  9. def test_use_streaming_false_for_no_databases():
  10. assert not module.use_streaming(databases=[], config=flexmock())
  11. def test_dump_data_sources_logs_and_skips_if_dump_already_exists():
  12. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  13. flexmock()
  14. )
  15. databases = [{'path': '/path/to/database', 'name': 'database'}]
  16. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  17. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  18. '/run/borgmatic/database'
  19. )
  20. flexmock(module.os.path).should_receive('exists').and_return(True)
  21. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  22. flexmock(module).should_receive('execute_command').never()
  23. assert (
  24. module.dump_data_sources(
  25. databases,
  26. {},
  27. config_paths=('test.yaml',),
  28. borgmatic_runtime_directory='/run/borgmatic',
  29. patterns=[],
  30. dry_run=False,
  31. )
  32. == []
  33. )
  34. def test_dump_data_sources_dumps_each_database():
  35. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  36. flexmock()
  37. )
  38. databases = [
  39. {'path': '/path/to/database1', 'name': 'database1'},
  40. {'path': '/path/to/database2', 'name': 'database2'},
  41. ]
  42. processes = [flexmock(), flexmock()]
  43. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  44. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  45. '/run/borgmatic/database'
  46. )
  47. flexmock(module.os.path).should_receive('exists').and_return(False)
  48. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  49. flexmock(module).should_receive('execute_command').and_return(processes[0]).and_return(
  50. processes[1]
  51. )
  52. assert (
  53. module.dump_data_sources(
  54. databases,
  55. {},
  56. config_paths=('test.yaml',),
  57. borgmatic_runtime_directory='/run/borgmatic',
  58. patterns=[],
  59. dry_run=False,
  60. )
  61. == processes
  62. )
  63. def test_dump_data_sources_with_path_injection_attack_gets_escaped():
  64. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  65. flexmock()
  66. )
  67. databases = [
  68. {'path': '/path/to/database1; naughty-command', 'name': 'database1'},
  69. ]
  70. processes = [flexmock()]
  71. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  72. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  73. '/run/borgmatic/database'
  74. )
  75. flexmock(module.os.path).should_receive('exists').and_return(False)
  76. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  77. flexmock(module).should_receive('execute_command').with_args(
  78. (
  79. 'sqlite3',
  80. "'/path/to/database1; naughty-command'",
  81. '.dump',
  82. '>',
  83. '/run/borgmatic/database',
  84. ),
  85. shell=True,
  86. run_to_completion=False,
  87. ).and_return(processes[0])
  88. assert (
  89. module.dump_data_sources(
  90. databases,
  91. {},
  92. config_paths=('test.yaml',),
  93. borgmatic_runtime_directory='/run/borgmatic',
  94. patterns=[],
  95. dry_run=False,
  96. )
  97. == processes
  98. )
  99. def test_dump_data_sources_with_non_existent_path_warns_and_dumps_database():
  100. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  101. flexmock()
  102. )
  103. databases = [
  104. {'path': '/path/to/database1', 'name': 'database1'},
  105. ]
  106. processes = [flexmock()]
  107. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  108. flexmock(module.logger).should_receive('warning').once()
  109. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  110. '/run/borgmatic'
  111. )
  112. flexmock(module.os.path).should_receive('exists').and_return(False)
  113. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  114. flexmock(module).should_receive('execute_command').and_return(processes[0])
  115. assert (
  116. module.dump_data_sources(
  117. databases,
  118. {},
  119. config_paths=('test.yaml',),
  120. borgmatic_runtime_directory='/run/borgmatic',
  121. patterns=[],
  122. dry_run=False,
  123. )
  124. == processes
  125. )
  126. def test_dump_data_sources_with_name_all_warns_and_dumps_all_databases():
  127. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  128. flexmock()
  129. )
  130. databases = [
  131. {'path': '/path/to/database1', 'name': 'all'},
  132. ]
  133. processes = [flexmock()]
  134. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  135. flexmock(module.logger).should_receive(
  136. 'warning'
  137. ).twice() # once for the name=all, once for the non-existent path
  138. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  139. '/run/borgmatic/database'
  140. )
  141. flexmock(module.os.path).should_receive('exists').and_return(False)
  142. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  143. flexmock(module).should_receive('execute_command').and_return(processes[0])
  144. assert (
  145. module.dump_data_sources(
  146. databases,
  147. {},
  148. config_paths=('test.yaml',),
  149. borgmatic_runtime_directory='/run/borgmatic',
  150. patterns=[],
  151. dry_run=False,
  152. )
  153. == processes
  154. )
  155. def test_dump_data_sources_does_not_dump_if_dry_run():
  156. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  157. flexmock()
  158. )
  159. databases = [{'path': '/path/to/database', 'name': 'database'}]
  160. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  161. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  162. '/run/borgmatic'
  163. )
  164. flexmock(module.os.path).should_receive('exists').and_return(False)
  165. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  166. flexmock(module).should_receive('execute_command').never()
  167. assert (
  168. module.dump_data_sources(
  169. databases,
  170. {},
  171. config_paths=('test.yaml',),
  172. borgmatic_runtime_directory='/run/borgmatic',
  173. patterns=[],
  174. dry_run=True,
  175. )
  176. == []
  177. )
  178. def test_restore_data_source_dump_restores_database():
  179. hook_config = [{'path': '/path/to/database', 'name': 'database'}, {'name': 'other'}]
  180. extract_process = flexmock(stdout=flexmock())
  181. flexmock(module).should_receive('execute_command_with_processes').with_args(
  182. (
  183. 'sqlite3',
  184. '/path/to/database',
  185. ),
  186. processes=[extract_process],
  187. output_log_level=logging.DEBUG,
  188. input_file=extract_process.stdout,
  189. ).once()
  190. flexmock(module.os).should_receive('remove').once()
  191. module.restore_data_source_dump(
  192. hook_config,
  193. {},
  194. data_source=hook_config[0],
  195. dry_run=False,
  196. extract_process=extract_process,
  197. connection_params={'restore_path': None},
  198. borgmatic_runtime_directory='/run/borgmatic',
  199. )
  200. def test_restore_data_source_dump_with_connection_params_uses_connection_params_for_restore():
  201. hook_config = [
  202. {'path': '/path/to/database', 'name': 'database', 'restore_path': 'config/path/to/database'}
  203. ]
  204. extract_process = flexmock(stdout=flexmock())
  205. flexmock(module).should_receive('execute_command_with_processes').with_args(
  206. (
  207. 'sqlite3',
  208. 'cli/path/to/database',
  209. ),
  210. processes=[extract_process],
  211. output_log_level=logging.DEBUG,
  212. input_file=extract_process.stdout,
  213. ).once()
  214. flexmock(module.os).should_receive('remove').once()
  215. module.restore_data_source_dump(
  216. hook_config,
  217. {},
  218. data_source={'name': 'database'},
  219. dry_run=False,
  220. extract_process=extract_process,
  221. connection_params={'restore_path': 'cli/path/to/database'},
  222. borgmatic_runtime_directory='/run/borgmatic',
  223. )
  224. def test_restore_data_source_dump_without_connection_params_uses_restore_params_in_config_for_restore():
  225. hook_config = [
  226. {'path': '/path/to/database', 'name': 'database', 'restore_path': 'config/path/to/database'}
  227. ]
  228. extract_process = flexmock(stdout=flexmock())
  229. flexmock(module).should_receive('execute_command_with_processes').with_args(
  230. (
  231. 'sqlite3',
  232. 'config/path/to/database',
  233. ),
  234. processes=[extract_process],
  235. output_log_level=logging.DEBUG,
  236. input_file=extract_process.stdout,
  237. ).once()
  238. flexmock(module.os).should_receive('remove').once()
  239. module.restore_data_source_dump(
  240. hook_config,
  241. {},
  242. data_source=hook_config[0],
  243. dry_run=False,
  244. extract_process=extract_process,
  245. connection_params={'restore_path': None},
  246. borgmatic_runtime_directory='/run/borgmatic',
  247. )
  248. def test_restore_data_source_dump_does_not_restore_database_if_dry_run():
  249. hook_config = [{'path': '/path/to/database', 'name': 'database'}]
  250. extract_process = flexmock(stdout=flexmock())
  251. flexmock(module).should_receive('execute_command_with_processes').never()
  252. flexmock(module.os).should_receive('remove').never()
  253. module.restore_data_source_dump(
  254. hook_config,
  255. {},
  256. data_source={'name': 'database'},
  257. dry_run=True,
  258. extract_process=extract_process,
  259. connection_params={'restore_path': None},
  260. borgmatic_runtime_directory='/run/borgmatic',
  261. )