test_repo_create.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. import logging
  2. import subprocess
  3. import pytest
  4. from flexmock import flexmock
  5. from borgmatic.borg import repo_create as module
  6. from ..test_verbosity import insert_logging_mock
  7. REPO_INFO_SOME_UNKNOWN_EXIT_CODE = -999
  8. REPO_CREATE_COMMAND = ('borg', 'repo-create', '--encryption', 'repokey')
  9. def insert_repo_info_command_found_mock():
  10. flexmock(module.repo_info).should_receive('display_repository_info').and_return(
  11. '{"encryption": {"mode": "repokey"}}',
  12. )
  13. def insert_repo_info_command_not_found_mock():
  14. flexmock(module.repo_info).should_receive('display_repository_info').and_raise(
  15. subprocess.CalledProcessError(
  16. sorted(module.REPO_INFO_REPOSITORY_NOT_FOUND_EXIT_CODES)[0],
  17. [],
  18. ),
  19. )
  20. def insert_repo_create_command_mock(
  21. repo_create_command,
  22. working_directory=None,
  23. borg_exit_codes=None,
  24. **kwargs,
  25. ):
  26. flexmock(module.environment).should_receive('make_environment')
  27. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
  28. working_directory,
  29. )
  30. flexmock(module).should_receive('execute_command').with_args(
  31. repo_create_command,
  32. output_file=module.DO_NOT_CAPTURE,
  33. environment=None,
  34. working_directory=working_directory,
  35. borg_local_path=repo_create_command[0],
  36. borg_exit_codes=borg_exit_codes,
  37. ).once()
  38. def test_create_repository_calls_borg_with_flags():
  39. insert_repo_info_command_not_found_mock()
  40. insert_repo_create_command_mock((*REPO_CREATE_COMMAND, '--repo', 'repo'))
  41. flexmock(module.feature).should_receive('available').and_return(True)
  42. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  43. (
  44. '--repo',
  45. 'repo',
  46. ),
  47. )
  48. module.create_repository(
  49. dry_run=False,
  50. repository_path='repo',
  51. config={},
  52. local_borg_version='2.3.4',
  53. global_arguments=flexmock(),
  54. encryption_mode='repokey',
  55. )
  56. def test_create_repository_with_dry_run_skips_borg_call():
  57. insert_repo_info_command_not_found_mock()
  58. flexmock(module).should_receive('execute_command').never()
  59. flexmock(module.feature).should_receive('available').and_return(True)
  60. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  61. (
  62. '--repo',
  63. 'repo',
  64. ),
  65. )
  66. module.create_repository(
  67. dry_run=True,
  68. repository_path='repo',
  69. config={},
  70. local_borg_version='2.3.4',
  71. global_arguments=flexmock(),
  72. encryption_mode='repokey',
  73. )
  74. def test_create_repository_raises_for_borg_repo_create_error():
  75. insert_repo_info_command_not_found_mock()
  76. flexmock(module.feature).should_receive('available').and_return(True)
  77. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  78. (
  79. '--repo',
  80. 'repo',
  81. ),
  82. )
  83. flexmock(module.environment).should_receive('make_environment')
  84. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  85. flexmock(module).should_receive('execute_command').and_raise(
  86. module.subprocess.CalledProcessError(2, 'borg repo_create'),
  87. )
  88. with pytest.raises(subprocess.CalledProcessError):
  89. module.create_repository(
  90. dry_run=False,
  91. repository_path='repo',
  92. config={},
  93. local_borg_version='2.3.4',
  94. global_arguments=flexmock(),
  95. encryption_mode='repokey',
  96. )
  97. def test_create_repository_skips_creation_when_repository_already_exists():
  98. insert_repo_info_command_found_mock()
  99. flexmock(module.feature).should_receive('available').and_return(True)
  100. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  101. (
  102. '--repo',
  103. 'repo',
  104. ),
  105. )
  106. module.create_repository(
  107. dry_run=False,
  108. repository_path='repo',
  109. config={},
  110. local_borg_version='2.3.4',
  111. global_arguments=flexmock(),
  112. encryption_mode='repokey',
  113. )
  114. def test_create_repository_errors_when_repository_with_differing_encryption_mode_already_exists():
  115. insert_repo_info_command_found_mock()
  116. flexmock(module.feature).should_receive('available').and_return(True)
  117. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  118. (
  119. '--repo',
  120. 'repo',
  121. ),
  122. )
  123. with pytest.raises(ValueError):
  124. module.create_repository(
  125. dry_run=False,
  126. repository_path='repo',
  127. config={},
  128. local_borg_version='2.3.4',
  129. global_arguments=flexmock(),
  130. encryption_mode='repokey-blake2',
  131. )
  132. def test_create_repository_raises_for_unknown_repo_info_command_error():
  133. flexmock(module.repo_info).should_receive('display_repository_info').and_raise(
  134. subprocess.CalledProcessError(REPO_INFO_SOME_UNKNOWN_EXIT_CODE, []),
  135. )
  136. with pytest.raises(subprocess.CalledProcessError):
  137. module.create_repository(
  138. dry_run=False,
  139. repository_path='repo',
  140. config={},
  141. local_borg_version='2.3.4',
  142. global_arguments=flexmock(),
  143. encryption_mode='repokey',
  144. )
  145. def test_create_repository_with_source_repository_calls_borg_with_other_repo_flag():
  146. insert_repo_info_command_not_found_mock()
  147. insert_repo_create_command_mock(
  148. (*REPO_CREATE_COMMAND, '--other-repo', 'other.borg', '--repo', 'repo'),
  149. )
  150. flexmock(module.feature).should_receive('available').and_return(True)
  151. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  152. (
  153. '--repo',
  154. 'repo',
  155. ),
  156. )
  157. module.create_repository(
  158. dry_run=False,
  159. repository_path='repo',
  160. config={},
  161. local_borg_version='2.3.4',
  162. global_arguments=flexmock(),
  163. encryption_mode='repokey',
  164. source_repository='other.borg',
  165. )
  166. def test_create_repository_with_copy_crypt_key_calls_borg_with_copy_crypt_key_flag():
  167. insert_repo_info_command_not_found_mock()
  168. insert_repo_create_command_mock((*REPO_CREATE_COMMAND, '--copy-crypt-key', '--repo', 'repo'))
  169. flexmock(module.feature).should_receive('available').and_return(True)
  170. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  171. (
  172. '--repo',
  173. 'repo',
  174. ),
  175. )
  176. module.create_repository(
  177. dry_run=False,
  178. repository_path='repo',
  179. config={},
  180. local_borg_version='2.3.4',
  181. global_arguments=flexmock(),
  182. encryption_mode='repokey',
  183. copy_crypt_key=True,
  184. )
  185. def test_create_repository_with_append_only_calls_borg_with_append_only_flag():
  186. insert_repo_info_command_not_found_mock()
  187. insert_repo_create_command_mock((*REPO_CREATE_COMMAND, '--append-only', '--repo', 'repo'))
  188. flexmock(module.feature).should_receive('available').and_return(True)
  189. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  190. (
  191. '--repo',
  192. 'repo',
  193. ),
  194. )
  195. module.create_repository(
  196. dry_run=False,
  197. repository_path='repo',
  198. config={'append_only': True},
  199. local_borg_version='2.3.4',
  200. global_arguments=flexmock(),
  201. encryption_mode='repokey',
  202. append_only=True,
  203. )
  204. def test_create_repository_with_append_only_config_calls_borg_with_append_only_flag():
  205. insert_repo_info_command_not_found_mock()
  206. insert_repo_create_command_mock((*REPO_CREATE_COMMAND, '--append-only', '--repo', 'repo'))
  207. flexmock(module.feature).should_receive('available').and_return(True)
  208. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  209. (
  210. '--repo',
  211. 'repo',
  212. ),
  213. )
  214. module.create_repository(
  215. dry_run=False,
  216. repository_path='repo',
  217. config={'append_only': True},
  218. local_borg_version='2.3.4',
  219. global_arguments=flexmock(),
  220. encryption_mode='repokey',
  221. append_only=True,
  222. )
  223. def test_create_repository_with_storage_quota_calls_borg_with_storage_quota_flag():
  224. insert_repo_info_command_not_found_mock()
  225. insert_repo_create_command_mock(
  226. (*REPO_CREATE_COMMAND, '--storage-quota', '5G', '--repo', 'repo'),
  227. )
  228. flexmock(module.feature).should_receive('available').and_return(True)
  229. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  230. (
  231. '--repo',
  232. 'repo',
  233. ),
  234. )
  235. module.create_repository(
  236. dry_run=False,
  237. repository_path='repo',
  238. config={'storage_quota': '5G'},
  239. local_borg_version='2.3.4',
  240. global_arguments=flexmock(),
  241. encryption_mode='repokey',
  242. storage_quota='5G',
  243. )
  244. def test_create_repository_with_make_parent_dirs_calls_borg_with_make_parent_dirs_flag():
  245. insert_repo_info_command_not_found_mock()
  246. insert_repo_create_command_mock((*REPO_CREATE_COMMAND, '--make-parent-dirs', '--repo', 'repo'))
  247. flexmock(module.feature).should_receive('available').and_return(True)
  248. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  249. (
  250. '--repo',
  251. 'repo',
  252. ),
  253. )
  254. module.create_repository(
  255. dry_run=False,
  256. repository_path='repo',
  257. config={'make_parent_directories': True},
  258. local_borg_version='2.3.4',
  259. global_arguments=flexmock(),
  260. encryption_mode='repokey',
  261. make_parent_directories=True,
  262. )
  263. def test_create_repository_with_log_info_calls_borg_with_info_flag():
  264. insert_repo_info_command_not_found_mock()
  265. insert_repo_create_command_mock((*REPO_CREATE_COMMAND, '--info', '--repo', 'repo'))
  266. insert_logging_mock(logging.INFO)
  267. flexmock(module.feature).should_receive('available').and_return(True)
  268. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  269. (
  270. '--repo',
  271. 'repo',
  272. ),
  273. )
  274. module.create_repository(
  275. dry_run=False,
  276. repository_path='repo',
  277. config={},
  278. local_borg_version='2.3.4',
  279. global_arguments=flexmock(),
  280. encryption_mode='repokey',
  281. )
  282. def test_create_repository_with_log_debug_calls_borg_with_debug_flag():
  283. insert_repo_info_command_not_found_mock()
  284. insert_repo_create_command_mock((*REPO_CREATE_COMMAND, '--debug', '--repo', 'repo'))
  285. insert_logging_mock(logging.DEBUG)
  286. flexmock(module.feature).should_receive('available').and_return(True)
  287. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  288. (
  289. '--repo',
  290. 'repo',
  291. ),
  292. )
  293. module.create_repository(
  294. dry_run=False,
  295. repository_path='repo',
  296. config={},
  297. local_borg_version='2.3.4',
  298. global_arguments=flexmock(),
  299. encryption_mode='repokey',
  300. )
  301. def test_create_repository_with_log_json_calls_borg_with_log_json_flag():
  302. insert_repo_info_command_not_found_mock()
  303. insert_repo_create_command_mock((*REPO_CREATE_COMMAND, '--log-json', '--repo', 'repo'))
  304. flexmock(module.feature).should_receive('available').and_return(True)
  305. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  306. (
  307. '--repo',
  308. 'repo',
  309. ),
  310. )
  311. module.create_repository(
  312. dry_run=False,
  313. repository_path='repo',
  314. config={'log_json': True},
  315. local_borg_version='2.3.4',
  316. global_arguments=flexmock(),
  317. encryption_mode='repokey',
  318. )
  319. def test_create_repository_with_lock_wait_calls_borg_with_lock_wait_flag():
  320. insert_repo_info_command_not_found_mock()
  321. insert_repo_create_command_mock((*REPO_CREATE_COMMAND, '--lock-wait', '5', '--repo', 'repo'))
  322. flexmock(module.feature).should_receive('available').and_return(True)
  323. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  324. (
  325. '--repo',
  326. 'repo',
  327. ),
  328. )
  329. module.create_repository(
  330. dry_run=False,
  331. repository_path='repo',
  332. config={'lock_wait': 5},
  333. local_borg_version='2.3.4',
  334. global_arguments=flexmock(),
  335. encryption_mode='repokey',
  336. )
  337. def test_create_repository_with_local_path_calls_borg_via_local_path():
  338. insert_repo_info_command_not_found_mock()
  339. insert_repo_create_command_mock(('borg1', *REPO_CREATE_COMMAND[1:], '--repo', 'repo'))
  340. flexmock(module.feature).should_receive('available').and_return(True)
  341. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  342. (
  343. '--repo',
  344. 'repo',
  345. ),
  346. )
  347. module.create_repository(
  348. dry_run=False,
  349. repository_path='repo',
  350. config={},
  351. local_borg_version='2.3.4',
  352. global_arguments=flexmock(),
  353. encryption_mode='repokey',
  354. local_path='borg1',
  355. )
  356. def test_create_repository_with_exit_codes_calls_borg_using_them():
  357. borg_exit_codes = flexmock()
  358. insert_repo_info_command_not_found_mock()
  359. insert_repo_create_command_mock(
  360. ('borg', *REPO_CREATE_COMMAND[1:], '--repo', 'repo'),
  361. borg_exit_codes=borg_exit_codes,
  362. )
  363. flexmock(module.feature).should_receive('available').and_return(True)
  364. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  365. (
  366. '--repo',
  367. 'repo',
  368. ),
  369. )
  370. module.create_repository(
  371. dry_run=False,
  372. repository_path='repo',
  373. config={'borg_exit_codes': borg_exit_codes},
  374. local_borg_version='2.3.4',
  375. global_arguments=flexmock(),
  376. encryption_mode='repokey',
  377. )
  378. def test_create_repository_with_remote_path_calls_borg_with_remote_path_flag():
  379. insert_repo_info_command_not_found_mock()
  380. insert_repo_create_command_mock(
  381. (*REPO_CREATE_COMMAND, '--remote-path', 'borg1', '--repo', 'repo'),
  382. )
  383. flexmock(module.feature).should_receive('available').and_return(True)
  384. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  385. (
  386. '--repo',
  387. 'repo',
  388. ),
  389. )
  390. module.create_repository(
  391. dry_run=False,
  392. repository_path='repo',
  393. config={},
  394. local_borg_version='2.3.4',
  395. global_arguments=flexmock(),
  396. encryption_mode='repokey',
  397. remote_path='borg1',
  398. )
  399. def test_create_repository_with_umask_calls_borg_with_umask_flag():
  400. insert_repo_info_command_not_found_mock()
  401. insert_repo_create_command_mock((*REPO_CREATE_COMMAND, '--umask', '077', '--repo', 'repo'))
  402. flexmock(module.feature).should_receive('available').and_return(True)
  403. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  404. (
  405. '--repo',
  406. 'repo',
  407. ),
  408. )
  409. module.create_repository(
  410. dry_run=False,
  411. repository_path='repo',
  412. config={'umask': '077'},
  413. local_borg_version='2.3.4',
  414. global_arguments=flexmock(),
  415. encryption_mode='repokey',
  416. )
  417. def test_create_repository_with_extra_borg_options_calls_borg_with_extra_options():
  418. insert_repo_info_command_not_found_mock()
  419. insert_repo_create_command_mock(
  420. (*REPO_CREATE_COMMAND, '--extra', '--options', 'value with space', '--repo', 'repo'),
  421. )
  422. flexmock(module.feature).should_receive('available').and_return(True)
  423. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  424. (
  425. '--repo',
  426. 'repo',
  427. ),
  428. )
  429. module.create_repository(
  430. dry_run=False,
  431. repository_path='repo',
  432. config={'extra_borg_options': {'repo-create': '--extra --options "value with space"'}},
  433. local_borg_version='2.3.4',
  434. global_arguments=flexmock(),
  435. encryption_mode='repokey',
  436. )
  437. def test_create_repository_calls_borg_with_working_directory():
  438. insert_repo_info_command_not_found_mock()
  439. insert_repo_create_command_mock(
  440. (*REPO_CREATE_COMMAND, '--repo', 'repo'),
  441. working_directory='/working/dir',
  442. )
  443. flexmock(module.feature).should_receive('available').and_return(True)
  444. flexmock(module.flags).should_receive('make_repository_flags').and_return(
  445. (
  446. '--repo',
  447. 'repo',
  448. ),
  449. )
  450. module.create_repository(
  451. dry_run=False,
  452. repository_path='repo',
  453. config={'working_directory': '/working/dir'},
  454. local_borg_version='2.3.4',
  455. global_arguments=flexmock(),
  456. encryption_mode='repokey',
  457. )