test_sqlite.py 9.5 KB

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