test_normalize.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. import pytest
  2. from flexmock import flexmock
  3. from borgmatic.config import normalize as module
  4. @pytest.mark.parametrize(
  5. 'config,expected_config,produces_logs',
  6. (
  7. (
  8. {'location': {'foo': 'bar', 'baz': 'quux'}},
  9. {'foo': 'bar', 'baz': 'quux'},
  10. True,
  11. ),
  12. (
  13. {'retention': {'foo': 'bar', 'baz': 'quux'}},
  14. {'foo': 'bar', 'baz': 'quux'},
  15. True,
  16. ),
  17. (
  18. {'consistency': {'foo': 'bar', 'baz': 'quux'}},
  19. {'foo': 'bar', 'baz': 'quux'},
  20. True,
  21. ),
  22. (
  23. {'output': {'foo': 'bar', 'baz': 'quux'}},
  24. {'foo': 'bar', 'baz': 'quux'},
  25. True,
  26. ),
  27. (
  28. {'hooks': {'foo': 'bar', 'baz': 'quux'}},
  29. {'foo': 'bar', 'baz': 'quux'},
  30. True,
  31. ),
  32. (
  33. {'location': {'foo': 'bar'}, 'storage': {'baz': 'quux'}},
  34. {'foo': 'bar', 'baz': 'quux'},
  35. True,
  36. ),
  37. (
  38. {'foo': 'bar', 'baz': 'quux'},
  39. {'foo': 'bar', 'baz': 'quux'},
  40. False,
  41. ),
  42. (
  43. {'location': {'prefix': 'foo'}, 'consistency': {'prefix': 'foo'}},
  44. {'prefix': 'foo'},
  45. True,
  46. ),
  47. (
  48. {'location': {'prefix': 'foo'}, 'consistency': {'prefix': 'foo'}},
  49. {'prefix': 'foo'},
  50. True,
  51. ),
  52. (
  53. {'location': {'prefix': 'foo'}, 'consistency': {'bar': 'baz'}},
  54. {'prefix': 'foo', 'bar': 'baz'},
  55. True,
  56. ),
  57. (
  58. {'storage': {'umask': 'foo'}, 'hooks': {'umask': 'foo'}},
  59. {'umask': 'foo'},
  60. True,
  61. ),
  62. (
  63. {'storage': {'umask': 'foo'}, 'hooks': {'umask': 'foo'}},
  64. {'umask': 'foo'},
  65. True,
  66. ),
  67. (
  68. {'storage': {'umask': 'foo'}, 'hooks': {'bar': 'baz'}},
  69. {'umask': 'foo', 'bar': 'baz'},
  70. True,
  71. ),
  72. (
  73. {'location': {'bar': 'baz'}, 'consistency': {'prefix': 'foo'}},
  74. {'bar': 'baz', 'prefix': 'foo'},
  75. True,
  76. ),
  77. (
  78. {'location': {}, 'consistency': {'prefix': 'foo'}},
  79. {'prefix': 'foo'},
  80. True,
  81. ),
  82. (
  83. {},
  84. {},
  85. False,
  86. ),
  87. ),
  88. )
  89. def test_normalize_sections_moves_section_options_to_global_scope(
  90. config,
  91. expected_config,
  92. produces_logs,
  93. ):
  94. logs = module.normalize_sections('test.yaml', config)
  95. assert config == expected_config
  96. if produces_logs:
  97. assert logs
  98. else:
  99. assert logs == []
  100. def test_normalize_sections_with_different_prefix_values_raises():
  101. config = {'location': {'prefix': 'foo'}, 'consistency': {'prefix': 'bar'}}
  102. with pytest.raises(ValueError):
  103. module.normalize_sections('test.yaml', config)
  104. def test_normalize_sections_with_different_umask_values_raises():
  105. config = {'storage': {'umask': 'foo'}, 'hooks': {'umask': 'bar'}}
  106. with pytest.raises(ValueError):
  107. module.normalize_sections('test.yaml', config)
  108. def test_normalize_sections_with_only_scalar_raises():
  109. config = 33
  110. with pytest.raises(ValueError):
  111. module.normalize_sections('test.yaml', config)
  112. @pytest.mark.parametrize(
  113. 'config,expected_config,produces_logs',
  114. (
  115. (
  116. {'before_actions': ['foo', 'bar'], 'after_actions': ['baz']},
  117. {
  118. 'commands': [
  119. {'before': 'repository', 'run': ['foo', 'bar']},
  120. {'after': 'repository', 'run': ['baz']},
  121. ],
  122. },
  123. True,
  124. ),
  125. (
  126. {'before_backup': ['foo', 'bar'], 'after_backup': ['baz']},
  127. {
  128. 'commands': [
  129. {'before': 'action', 'when': ['create'], 'run': ['foo', 'bar']},
  130. {'after': 'action', 'when': ['create'], 'run': ['baz']},
  131. ],
  132. },
  133. True,
  134. ),
  135. (
  136. {'before_prune': ['foo', 'bar'], 'after_prune': ['baz']},
  137. {
  138. 'commands': [
  139. {'before': 'action', 'when': ['prune'], 'run': ['foo', 'bar']},
  140. {'after': 'action', 'when': ['prune'], 'run': ['baz']},
  141. ],
  142. },
  143. True,
  144. ),
  145. (
  146. {'before_compact': ['foo', 'bar'], 'after_compact': ['baz']},
  147. {
  148. 'commands': [
  149. {'before': 'action', 'when': ['compact'], 'run': ['foo', 'bar']},
  150. {'after': 'action', 'when': ['compact'], 'run': ['baz']},
  151. ],
  152. },
  153. True,
  154. ),
  155. (
  156. {'before_check': ['foo', 'bar'], 'after_check': ['baz']},
  157. {
  158. 'commands': [
  159. {'before': 'action', 'when': ['check'], 'run': ['foo', 'bar']},
  160. {'after': 'action', 'when': ['check'], 'run': ['baz']},
  161. ],
  162. },
  163. True,
  164. ),
  165. (
  166. {'before_extract': ['foo', 'bar'], 'after_extract': ['baz']},
  167. {
  168. 'commands': [
  169. {'before': 'action', 'when': ['extract'], 'run': ['foo', 'bar']},
  170. {'after': 'action', 'when': ['extract'], 'run': ['baz']},
  171. ],
  172. },
  173. True,
  174. ),
  175. (
  176. {'on_error': ['foo', 'bar']},
  177. {
  178. 'commands': [
  179. {
  180. 'after': 'error',
  181. 'when': ['create', 'prune', 'compact', 'check'],
  182. 'run': ['foo', 'bar'],
  183. },
  184. ],
  185. },
  186. True,
  187. ),
  188. (
  189. {'before_everything': ['foo', 'bar'], 'after_everything': ['baz']},
  190. {
  191. 'commands': [
  192. {'before': 'everything', 'when': ['create'], 'run': ['foo', 'bar']},
  193. {'after': 'everything', 'when': ['create'], 'run': ['baz']},
  194. ],
  195. },
  196. True,
  197. ),
  198. (
  199. {'other': 'options', 'unrelated_to': 'commands'},
  200. {'other': 'options', 'unrelated_to': 'commands'},
  201. False,
  202. ),
  203. ),
  204. )
  205. def test_normalize_commands_moves_individual_command_hooks_to_unified_commands(
  206. config,
  207. expected_config,
  208. produces_logs,
  209. ):
  210. flexmock(module).should_receive('make_command_hook_deprecation_log').and_return(flexmock())
  211. logs = module.normalize_commands('test.yaml', config)
  212. assert config == expected_config
  213. if produces_logs:
  214. assert logs
  215. else:
  216. assert logs == []
  217. @pytest.mark.parametrize(
  218. 'config,expected_config,produces_logs',
  219. (
  220. (
  221. {'exclude_if_present': '.nobackup'},
  222. {'exclude_if_present': ['.nobackup']},
  223. True,
  224. ),
  225. (
  226. {'exclude_if_present': ['.nobackup']},
  227. {'exclude_if_present': ['.nobackup']},
  228. False,
  229. ),
  230. (
  231. {'store_config_files': False},
  232. {'bootstrap': {'store_config_files': False}},
  233. True,
  234. ),
  235. (
  236. {'source_directories': ['foo', 'bar']},
  237. {'source_directories': ['foo', 'bar']},
  238. False,
  239. ),
  240. (
  241. {'compression': 'yes_please'},
  242. {'compression': 'yes_please'},
  243. False,
  244. ),
  245. (
  246. {'healthchecks': 'https://example.com'},
  247. {'healthchecks': {'ping_url': 'https://example.com'}},
  248. True,
  249. ),
  250. (
  251. {'cronitor': 'https://example.com'},
  252. {'cronitor': {'ping_url': 'https://example.com'}},
  253. True,
  254. ),
  255. (
  256. {'pagerduty': 'https://example.com'},
  257. {'pagerduty': {'integration_key': 'https://example.com'}},
  258. True,
  259. ),
  260. (
  261. {'cronhub': 'https://example.com'},
  262. {'cronhub': {'ping_url': 'https://example.com'}},
  263. True,
  264. ),
  265. (
  266. {'checks': ['archives']},
  267. {'checks': [{'name': 'archives'}]},
  268. True,
  269. ),
  270. (
  271. {'checks': ['archives']},
  272. {'checks': [{'name': 'archives'}]},
  273. True,
  274. ),
  275. (
  276. {'numeric_owner': False},
  277. {'numeric_ids': False},
  278. True,
  279. ),
  280. (
  281. {'bsd_flags': False},
  282. {'flags': False},
  283. True,
  284. ),
  285. (
  286. {'remote_rate_limit': False},
  287. {'upload_rate_limit': False},
  288. True,
  289. ),
  290. (
  291. {'repositories': ['foo@bar:/repo']},
  292. {'repositories': [{'path': 'ssh://foo@bar/repo'}]},
  293. True,
  294. ),
  295. (
  296. {'repositories': ['foo@bar:repo']},
  297. {'repositories': [{'path': 'ssh://foo@bar/./repo'}]},
  298. True,
  299. ),
  300. (
  301. {'repositories': ['foo@bar:~/repo']},
  302. {'repositories': [{'path': 'ssh://foo@bar/~/repo'}]},
  303. True,
  304. ),
  305. (
  306. {'repositories': ['ssh://foo@bar:1234/repo']},
  307. {'repositories': [{'path': 'ssh://foo@bar:1234/repo'}]},
  308. True,
  309. ),
  310. (
  311. {'repositories': ['sftp://foo@bar:1234/repo']},
  312. {'repositories': [{'path': 'sftp://foo@bar:1234/repo'}]},
  313. True,
  314. ),
  315. (
  316. {'repositories': ['rclone:host:repo']},
  317. {'repositories': [{'path': 'rclone:host:repo'}]},
  318. True,
  319. ),
  320. (
  321. {'repositories': ['s3:stuff']},
  322. {'repositories': [{'path': 's3:stuff'}]},
  323. True,
  324. ),
  325. (
  326. {'repositories': ['b2:stuff']},
  327. {'repositories': [{'path': 'b2:stuff'}]},
  328. True,
  329. ),
  330. (
  331. {'repositories': ['file:///repo']},
  332. {'repositories': [{'path': '/repo'}]},
  333. True,
  334. ),
  335. (
  336. {'repositories': [{'path': 'first'}, 'file:///repo']},
  337. {'repositories': [{'path': 'first'}, {'path': '/repo'}]},
  338. True,
  339. ),
  340. (
  341. {'repositories': [{'path': 'foo@bar:/repo', 'label': 'foo'}]},
  342. {'repositories': [{'path': 'ssh://foo@bar/repo', 'label': 'foo'}]},
  343. True,
  344. ),
  345. (
  346. {'repositories': [{'path': 'file:///repo', 'label': 'foo'}]},
  347. {'repositories': [{'path': '/repo', 'label': 'foo'}]},
  348. False,
  349. ),
  350. (
  351. {'repositories': [{'path': '/repo', 'label': 'foo'}]},
  352. {'repositories': [{'path': '/repo', 'label': 'foo'}]},
  353. False,
  354. ),
  355. (
  356. {'repositories': [{'path': None, 'label': 'foo'}]},
  357. {'repositories': []},
  358. False,
  359. ),
  360. (
  361. {'prefix': 'foo'},
  362. {'prefix': 'foo'},
  363. True,
  364. ),
  365. ),
  366. )
  367. def test_normalize_applies_hard_coded_normalization_to_config(
  368. config,
  369. expected_config,
  370. produces_logs,
  371. ):
  372. flexmock(module).should_receive('normalize_sections').and_return([])
  373. flexmock(module).should_receive('normalize_commands').and_return([])
  374. logs = module.normalize('test.yaml', config)
  375. expected_config.setdefault('bootstrap', {})
  376. assert config == expected_config
  377. if produces_logs:
  378. assert logs
  379. else:
  380. assert logs == []
  381. def test_normalize_config_with_borgmatic_source_directory_warns():
  382. flexmock(module).should_receive('normalize_sections').and_return([])
  383. flexmock(module).should_receive('normalize_commands').and_return([])
  384. logs = module.normalize('test.yaml', {'borgmatic_source_directory': '~/.borgmatic'})
  385. assert len(logs) == 1
  386. assert logs[0].levelno == module.logging.WARNING
  387. assert 'borgmatic_source_directory' in logs[0].msg