test_sqlite.py 9.3 KB

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