test_database.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. import json
  2. import os
  3. import shutil
  4. import subprocess
  5. import sys
  6. import tempfile
  7. import pymongo
  8. import pytest
  9. import ruamel.yaml
  10. def write_configuration(
  11. source_directory,
  12. config_path,
  13. repository_path,
  14. user_runtime_directory,
  15. postgresql_dump_format='custom',
  16. postgresql_all_dump_format=None,
  17. mariadb_mysql_all_dump_format=None,
  18. mongodb_dump_format='archive',
  19. ):
  20. '''
  21. Write out borgmatic configuration into a file at the config path. Set the options so as to work
  22. for testing. This includes injecting the given repository path, borgmatic source directory for
  23. storing database dumps, dump format (for PostgreSQL), and encryption passphrase.
  24. '''
  25. postgresql_all_format_option = (
  26. f'format: {postgresql_all_dump_format}' if postgresql_all_dump_format else ''
  27. )
  28. mariadb_mysql_dump_format_option = (
  29. f'format: {mariadb_mysql_all_dump_format}' if mariadb_mysql_all_dump_format else ''
  30. )
  31. config_yaml = f'''
  32. source_directories:
  33. - {source_directory}
  34. repositories:
  35. - path: {repository_path}
  36. user_runtime_directory: {user_runtime_directory}
  37. encryption_passphrase: "test"
  38. postgresql_databases:
  39. - name: test
  40. hostname: postgresql
  41. username: postgres
  42. password: test
  43. format: {postgresql_dump_format}
  44. - name: all
  45. {postgresql_all_format_option}
  46. hostname: postgresql
  47. username: postgres
  48. password: test
  49. mariadb_databases:
  50. - name: test
  51. hostname: mariadb
  52. username: root
  53. password: test
  54. - name: all
  55. {mariadb_mysql_dump_format_option}
  56. hostname: mariadb
  57. username: root
  58. password: test
  59. mysql_databases:
  60. - name: test
  61. hostname: not-actually-mysql
  62. username: root
  63. password: test
  64. - name: all
  65. {mariadb_mysql_dump_format_option}
  66. hostname: not-actually-mysql
  67. username: root
  68. password: test
  69. mongodb_databases:
  70. - name: test
  71. hostname: mongodb
  72. username: root
  73. password: test
  74. authentication_database: admin
  75. format: {mongodb_dump_format}
  76. - name: all
  77. hostname: mongodb
  78. username: root
  79. password: test
  80. sqlite_databases:
  81. - name: sqlite_test
  82. path: /tmp/sqlite_test.db
  83. '''
  84. with open(config_path, 'w') as config_file:
  85. config_file.write(config_yaml)
  86. return ruamel.yaml.YAML(typ='safe').load(config_yaml)
  87. def write_container_configuration(
  88. source_directory,
  89. config_path,
  90. repository_path,
  91. user_runtime_directory,
  92. ):
  93. '''
  94. Write out borgmatic configuration into a file at the config path. Set the options so as to work
  95. for testing. This includes injecting the given repository path, borgmatic source directory for
  96. storing database dumps, and encryption passphrase.
  97. '''
  98. config_yaml = f'''
  99. source_directories:
  100. - {source_directory}
  101. repositories:
  102. - path: {repository_path}
  103. user_runtime_directory: {user_runtime_directory}
  104. encryption_passphrase: "test"
  105. postgresql_databases:
  106. - name: test
  107. hostname: postgresql
  108. username: postgres
  109. password: test
  110. restore_hostname: postgresql2
  111. restore_port: 5433
  112. restore_password: test2
  113. mariadb_databases:
  114. - name: test
  115. hostname: mariadb
  116. username: root
  117. password: test
  118. restore_hostname: mariadb2
  119. restore_port: 3307
  120. restore_username: root
  121. restore_password: test2
  122. mysql_databases:
  123. - name: test
  124. hostname: not-actually-mysql
  125. username: root
  126. password: test
  127. mongodb_databases:
  128. - name: test
  129. hostname: mongodb
  130. username: root
  131. password: test
  132. authentication_database: admin
  133. sqlite_databases:
  134. - name: sqlite_test
  135. path: /tmp/sqlite_test.db
  136. '''
  137. with open(config_path, 'w') as config_file:
  138. config_file.write(config_yaml)
  139. return ruamel.yaml.YAML(typ='safe').load(config_yaml)
  140. @pytest.mark.parametrize(
  141. 'postgresql_all_dump_format,mariadb_mysql_all_dump_format',
  142. (
  143. (None, None),
  144. ('custom', 'sql'),
  145. ),
  146. )
  147. def write_custom_restore_configuration(
  148. source_directory,
  149. config_path,
  150. repository_path,
  151. user_runtime_directory,
  152. postgresql_dump_format='custom',
  153. postgresql_all_dump_format=None,
  154. mariadb_mysql_all_dump_format=None,
  155. mongodb_dump_format='archive',
  156. ):
  157. '''
  158. Write out borgmatic configuration into a file at the config path. Set the options so as to work
  159. for testing with custom restore options. This includes a custom restore_hostname, restore_port,
  160. restore_username, restore_password and restore_path.
  161. '''
  162. config_yaml = f'''
  163. source_directories:
  164. - {source_directory}
  165. repositories:
  166. - path: {repository_path}
  167. user_runtime_directory: {user_runtime_directory}
  168. encryption_passphrase: "test"
  169. postgresql_databases:
  170. - name: test
  171. hostname: postgresql
  172. username: postgres
  173. password: test
  174. format: {postgresql_dump_format}
  175. restore_hostname: postgresql2
  176. restore_port: 5433
  177. restore_password: test2
  178. mariadb_databases:
  179. - name: test
  180. hostname: mariadb
  181. username: root
  182. password: test
  183. restore_hostname: mariadb2
  184. restore_port: 3307
  185. restore_username: root
  186. restore_password: test2
  187. mysql_databases:
  188. - name: test
  189. hostname: not-actually-mysql
  190. username: root
  191. password: test
  192. restore_hostname: not-actually-mysql2
  193. restore_port: 3307
  194. restore_username: root
  195. restore_password: test2
  196. mongodb_databases:
  197. - name: test
  198. hostname: mongodb
  199. username: root
  200. password: test
  201. authentication_database: admin
  202. format: {mongodb_dump_format}
  203. restore_hostname: mongodb2
  204. restore_port: 27018
  205. restore_username: root2
  206. restore_password: test2
  207. sqlite_databases:
  208. - name: sqlite_test
  209. path: /tmp/sqlite_test.db
  210. restore_path: /tmp/sqlite_test2.db
  211. '''
  212. with open(config_path, 'w') as config_file:
  213. config_file.write(config_yaml)
  214. return ruamel.yaml.YAML(typ='safe').load(config_yaml)
  215. def write_simple_custom_restore_configuration(
  216. source_directory,
  217. config_path,
  218. repository_path,
  219. user_runtime_directory,
  220. postgresql_dump_format='custom',
  221. ):
  222. '''
  223. Write out borgmatic configuration into a file at the config path. Set the options so as to work
  224. for testing with custom restore options, but this time using CLI arguments. This includes a
  225. custom restore_hostname, restore_port, restore_username and restore_password as we only test
  226. these options for PostgreSQL.
  227. '''
  228. config_yaml = f'''
  229. source_directories:
  230. - {source_directory}
  231. repositories:
  232. - path: {repository_path}
  233. user_runtime_directory: {user_runtime_directory}
  234. encryption_passphrase: "test"
  235. postgresql_databases:
  236. - name: test
  237. hostname: postgresql
  238. username: postgres
  239. password: test
  240. format: {postgresql_dump_format}
  241. '''
  242. with open(config_path, 'w') as config_file:
  243. config_file.write(config_yaml)
  244. return ruamel.yaml.YAML(typ='safe').load(config_yaml)
  245. def get_connection_params(database, use_restore_options=False):
  246. hostname = (database.get('restore_hostname') if use_restore_options else None) or database.get(
  247. 'container',
  248. database.get('hostname'),
  249. )
  250. port = (database.get('restore_port') if use_restore_options else None) or database.get('port')
  251. username = (database.get('restore_username') if use_restore_options else None) or database.get(
  252. 'username',
  253. )
  254. password = (database.get('restore_password') if use_restore_options else None) or database.get(
  255. 'password',
  256. )
  257. return (hostname, port, username, password)
  258. def run_postgresql_command(command, config, use_restore_options=False):
  259. (hostname, port, username, password) = get_connection_params(
  260. config['postgresql_databases'][0],
  261. use_restore_options,
  262. )
  263. subprocess.check_call(
  264. [
  265. '/usr/bin/psql',
  266. f'--host={hostname}',
  267. f'--port={port or 5432}',
  268. f"--username={username or 'root'}",
  269. f'--command={command}',
  270. 'test',
  271. ],
  272. env={'PGPASSWORD': password},
  273. )
  274. def run_mariadb_command(command, config, use_restore_options=False, binary_name='mariadb'):
  275. (hostname, port, username, password) = get_connection_params(
  276. config[f'{binary_name}_databases'][0],
  277. use_restore_options,
  278. )
  279. subprocess.check_call(
  280. [
  281. f'/usr/bin/{binary_name}',
  282. f'--host={hostname}',
  283. f'--port={port or 3306}',
  284. f'--user={username}',
  285. f'--execute={command}',
  286. 'test',
  287. ],
  288. env={'MYSQL_PWD': password},
  289. )
  290. def get_mongodb_database_client(config, use_restore_options=False):
  291. (hostname, port, username, password) = get_connection_params(
  292. config['mongodb_databases'][0],
  293. use_restore_options,
  294. )
  295. return pymongo.MongoClient(f'mongodb://{username}:{password}@{hostname}:{port or 27017}').test
  296. def run_sqlite_command(command, config, use_restore_options=False):
  297. database = config['sqlite_databases'][0]
  298. path = (database.get('restore_path') if use_restore_options else None) or database.get('path')
  299. subprocess.check_call(
  300. [
  301. '/usr/bin/sqlite3',
  302. path,
  303. command,
  304. '.exit',
  305. ],
  306. )
  307. DEFAULT_HOOK_NAMES = {'postgresql', 'mariadb', 'mysql', 'mongodb', 'sqlite'}
  308. def create_test_tables(config, use_restore_options=False):
  309. '''
  310. Create test tables for borgmatic to dump and backup.
  311. '''
  312. command = 'create table test{id} (thing int); insert into test{id} values (1);'
  313. if 'postgresql_databases' in config:
  314. run_postgresql_command(command.format(id=1), config, use_restore_options)
  315. if 'mariadb_databases' in config:
  316. run_mariadb_command(command.format(id=2), config, use_restore_options)
  317. if 'mysql_databases' in config:
  318. run_mariadb_command(command.format(id=3), config, use_restore_options, binary_name='mysql')
  319. if 'mongodb_databases' in config:
  320. get_mongodb_database_client(config, use_restore_options)['test4'].insert_one({'thing': 1})
  321. if 'sqlite_databases' in config:
  322. run_sqlite_command(command.format(id=5), config, use_restore_options)
  323. def drop_test_tables(config, use_restore_options=False):
  324. '''
  325. Drop the test tables in preparation for borgmatic restoring them.
  326. '''
  327. command = 'drop table if exists test{id};'
  328. if 'postgresql_databases' in config:
  329. run_postgresql_command(command.format(id=1), config, use_restore_options)
  330. if 'mariadb_databases' in config:
  331. run_mariadb_command(command.format(id=2), config, use_restore_options)
  332. if 'mysql_databases' in config:
  333. run_mariadb_command(command.format(id=3), config, use_restore_options, binary_name='mysql')
  334. if 'mongodb_databases' in config:
  335. get_mongodb_database_client(config, use_restore_options)['test4'].drop()
  336. if 'sqlite_databases' in config:
  337. run_sqlite_command(command.format(id=5), config, use_restore_options)
  338. def select_test_tables(config, use_restore_options=False):
  339. '''
  340. Select the test tables to make sure they exist.
  341. Raise if the expected tables cannot be selected, for instance if a restore hasn't worked as
  342. expected.
  343. '''
  344. command = 'select count(*) from test{id};'
  345. if 'postgresql_databases' in config:
  346. run_postgresql_command(command.format(id=1), config, use_restore_options)
  347. if 'mariadb_databases' in config:
  348. run_mariadb_command(command.format(id=2), config, use_restore_options)
  349. if 'mysql_databases' in config:
  350. run_mariadb_command(command.format(id=3), config, use_restore_options, binary_name='mysql')
  351. if 'mongodb_databases' in config:
  352. assert (
  353. get_mongodb_database_client(config, use_restore_options)['test4'].count_documents(
  354. filter={},
  355. )
  356. > 0
  357. )
  358. if 'sqlite_databases' in config:
  359. run_sqlite_command(command.format(id=5), config, use_restore_options)
  360. def test_database_dump_and_restore():
  361. # Create a Borg repository.
  362. temporary_directory = tempfile.mkdtemp()
  363. repository_path = os.path.join(temporary_directory, 'test.borg')
  364. # Write out a special file to ensure that it gets properly excluded and Borg doesn't hang on it.
  365. os.mkfifo(os.path.join(temporary_directory, 'special_file'))
  366. original_working_directory = os.getcwd()
  367. try:
  368. config_path = os.path.join(temporary_directory, 'test.yaml')
  369. config = write_configuration(
  370. temporary_directory,
  371. config_path,
  372. repository_path,
  373. temporary_directory,
  374. )
  375. create_test_tables(config)
  376. select_test_tables(config)
  377. subprocess.check_call(
  378. [
  379. 'borgmatic',
  380. '-v',
  381. '2',
  382. '--config',
  383. config_path,
  384. 'repo-create',
  385. '--encryption',
  386. 'repokey',
  387. ],
  388. )
  389. # Run borgmatic to generate a backup archive including database dumps.
  390. subprocess.check_call(['borgmatic', 'create', '--config', config_path, '-v', '2'])
  391. # Get the created archive name.
  392. output = subprocess.check_output(
  393. ['borgmatic', '--config', config_path, 'list', '--json'],
  394. ).decode(sys.stdout.encoding)
  395. parsed_output = json.loads(output)
  396. assert len(parsed_output) == 1
  397. assert len(parsed_output[0]['archives']) == 1
  398. archive_name = parsed_output[0]['archives'][0]['archive']
  399. # Restore the databases from the archive.
  400. drop_test_tables(config)
  401. subprocess.check_call(
  402. ['borgmatic', '-v', '2', '--config', config_path, 'restore', '--archive', archive_name],
  403. )
  404. # Ensure the test tables have actually been restored.
  405. select_test_tables(config)
  406. finally:
  407. os.chdir(original_working_directory)
  408. shutil.rmtree(temporary_directory)
  409. drop_test_tables(config)
  410. def test_database_dump_and_restore_with_restore_cli_flags():
  411. # Create a Borg repository.
  412. temporary_directory = tempfile.mkdtemp()
  413. repository_path = os.path.join(temporary_directory, 'test.borg')
  414. original_working_directory = os.getcwd()
  415. try:
  416. config_path = os.path.join(temporary_directory, 'test.yaml')
  417. config = write_simple_custom_restore_configuration(
  418. temporary_directory,
  419. config_path,
  420. repository_path,
  421. temporary_directory,
  422. )
  423. create_test_tables(config)
  424. select_test_tables(config)
  425. subprocess.check_call(
  426. [
  427. 'borgmatic',
  428. '-v',
  429. '2',
  430. '--config',
  431. config_path,
  432. 'repo-create',
  433. '--encryption',
  434. 'repokey',
  435. ],
  436. )
  437. # Run borgmatic to generate a backup archive including a database dump.
  438. subprocess.check_call(['borgmatic', 'create', '--config', config_path, '-v', '2'])
  439. # Get the created archive name.
  440. output = subprocess.check_output(
  441. ['borgmatic', '--config', config_path, 'list', '--json'],
  442. ).decode(sys.stdout.encoding)
  443. parsed_output = json.loads(output)
  444. assert len(parsed_output) == 1
  445. assert len(parsed_output[0]['archives']) == 1
  446. archive_name = parsed_output[0]['archives'][0]['archive']
  447. # Restore the database from the archive.
  448. drop_test_tables(config)
  449. subprocess.check_call(
  450. [
  451. 'borgmatic',
  452. '-v',
  453. '2',
  454. '--config',
  455. config_path,
  456. 'restore',
  457. '--archive',
  458. archive_name,
  459. '--hostname',
  460. 'postgresql2',
  461. '--port',
  462. '5433',
  463. '--password',
  464. 'test2',
  465. ],
  466. )
  467. # Ensure the test tables have actually been restored. But first modify the config to contain
  468. # the altered restore values from the borgmatic command above. This ensures that the test
  469. # tables are selected from the correct database.
  470. database = config['postgresql_databases'][0]
  471. database['restore_hostname'] = 'postgresql2'
  472. database['restore_port'] = '5433'
  473. database['restore_password'] = 'test2'
  474. select_test_tables(config, use_restore_options=True)
  475. finally:
  476. os.chdir(original_working_directory)
  477. shutil.rmtree(temporary_directory)
  478. drop_test_tables(config)
  479. drop_test_tables(config, use_restore_options=True)
  480. def test_database_dump_and_restore_with_restore_configuration_options():
  481. # Create a Borg repository.
  482. temporary_directory = tempfile.mkdtemp()
  483. repository_path = os.path.join(temporary_directory, 'test.borg')
  484. original_working_directory = os.getcwd()
  485. try:
  486. config_path = os.path.join(temporary_directory, 'test.yaml')
  487. config = write_custom_restore_configuration(
  488. temporary_directory,
  489. config_path,
  490. repository_path,
  491. temporary_directory,
  492. )
  493. create_test_tables(config)
  494. select_test_tables(config)
  495. subprocess.check_call(
  496. [
  497. 'borgmatic',
  498. '-v',
  499. '2',
  500. '--config',
  501. config_path,
  502. 'repo-create',
  503. '--encryption',
  504. 'repokey',
  505. ],
  506. )
  507. # Run borgmatic to generate a backup archive including a database dump.
  508. subprocess.check_call(['borgmatic', 'create', '--config', config_path, '-v', '2'])
  509. # Get the created archive name.
  510. output = subprocess.check_output(
  511. ['borgmatic', '--config', config_path, 'list', '--json'],
  512. ).decode(sys.stdout.encoding)
  513. parsed_output = json.loads(output)
  514. assert len(parsed_output) == 1
  515. assert len(parsed_output[0]['archives']) == 1
  516. archive_name = parsed_output[0]['archives'][0]['archive']
  517. # Restore the database from the archive.
  518. drop_test_tables(config)
  519. subprocess.check_call(
  520. ['borgmatic', '-v', '2', '--config', config_path, 'restore', '--archive', archive_name],
  521. )
  522. # Ensure the test tables have actually been restored.
  523. select_test_tables(config, use_restore_options=True)
  524. finally:
  525. os.chdir(original_working_directory)
  526. shutil.rmtree(temporary_directory)
  527. drop_test_tables(config)
  528. drop_test_tables(config, use_restore_options=True)
  529. def test_database_dump_and_restore_with_directory_format():
  530. # Create a Borg repository.
  531. temporary_directory = tempfile.mkdtemp()
  532. repository_path = os.path.join(temporary_directory, 'test.borg')
  533. original_working_directory = os.getcwd()
  534. try:
  535. config_path = os.path.join(temporary_directory, 'test.yaml')
  536. config = write_configuration(
  537. temporary_directory,
  538. config_path,
  539. repository_path,
  540. temporary_directory,
  541. postgresql_dump_format='directory',
  542. mongodb_dump_format='directory',
  543. )
  544. create_test_tables(config)
  545. select_test_tables(config)
  546. subprocess.check_call(
  547. [
  548. 'borgmatic',
  549. '-v',
  550. '2',
  551. '--config',
  552. config_path,
  553. 'repo-create',
  554. '--encryption',
  555. 'repokey',
  556. ],
  557. )
  558. # Run borgmatic to generate a backup archive including a database dump.
  559. subprocess.check_call(['borgmatic', 'create', '--config', config_path, '-v', '2'])
  560. # Restore the database from the archive.
  561. drop_test_tables(config)
  562. subprocess.check_call(
  563. ['borgmatic', '--config', config_path, 'restore', '--archive', 'latest'],
  564. )
  565. # Ensure the test tables have actually been restored.
  566. select_test_tables(config)
  567. finally:
  568. os.chdir(original_working_directory)
  569. shutil.rmtree(temporary_directory)
  570. drop_test_tables(config)
  571. def test_database_dump_with_error_causes_borgmatic_to_exit():
  572. # Create a Borg repository.
  573. temporary_directory = tempfile.mkdtemp()
  574. repository_path = os.path.join(temporary_directory, 'test.borg')
  575. original_working_directory = os.getcwd()
  576. try:
  577. config_path = os.path.join(temporary_directory, 'test.yaml')
  578. write_configuration(temporary_directory, config_path, repository_path, temporary_directory)
  579. subprocess.check_call(
  580. [
  581. 'borgmatic',
  582. '-v',
  583. '2',
  584. '--config',
  585. config_path,
  586. 'repo-create',
  587. '--encryption',
  588. 'repokey',
  589. ],
  590. )
  591. # Run borgmatic with a config override such that the database dump fails.
  592. with pytest.raises(subprocess.CalledProcessError):
  593. subprocess.check_call(
  594. [
  595. 'borgmatic',
  596. 'create',
  597. '--config',
  598. config_path,
  599. '-v',
  600. '2',
  601. '--override',
  602. "hooks.postgresql_databases=[{'name': 'nope'}]",
  603. ],
  604. )
  605. finally:
  606. os.chdir(original_working_directory)
  607. shutil.rmtree(temporary_directory)
  608. def test_database_dump_and_restore_containers():
  609. # Create a Borg repository.
  610. temporary_directory = tempfile.mkdtemp()
  611. repository_path = os.path.join(temporary_directory, 'test.borg')
  612. original_working_directory = os.getcwd()
  613. original_path = os.environ.get('PATH', '')
  614. os.environ['PATH'] = f'/app/tests/end-to-end/commands:{original_path}'
  615. try:
  616. config_path = os.path.join(temporary_directory, 'test.yaml')
  617. config = write_container_configuration(
  618. temporary_directory,
  619. config_path,
  620. repository_path,
  621. temporary_directory,
  622. )
  623. create_test_tables(config)
  624. select_test_tables(config)
  625. subprocess.check_call(
  626. [
  627. 'borgmatic',
  628. '-v',
  629. '2',
  630. '--config',
  631. config_path,
  632. 'repo-create',
  633. '--encryption',
  634. 'repokey',
  635. ],
  636. )
  637. # Run borgmatic to generate a backup archive including database dumps.
  638. subprocess.check_call(['borgmatic', 'create', '--config', config_path, '-v', '2'])
  639. # Get the created archive name.
  640. output = subprocess.check_output(
  641. ['borgmatic', '--config', config_path, 'list', '--json'],
  642. ).decode(sys.stdout.encoding)
  643. parsed_output = json.loads(output)
  644. assert len(parsed_output) == 1
  645. assert len(parsed_output[0]['archives']) == 1
  646. archive_name = parsed_output[0]['archives'][0]['archive']
  647. # Restore the databases from the archive.
  648. drop_test_tables(config)
  649. subprocess.check_call(
  650. ['borgmatic', '-v', '2', '--config', config_path, 'restore', '--archive', archive_name],
  651. )
  652. # Ensure the test tables have actually been restored.
  653. select_test_tables(config, use_restore_options=True)
  654. finally:
  655. os.chdir(original_working_directory)
  656. shutil.rmtree(temporary_directory)
  657. drop_test_tables(config)
  658. os.environ['PATH'] = original_path