test_sqlite.py 8.8 KB

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