test_sqlite.py 9.5 KB

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