Support for mounting an archive as a FUSE filesystem via "borgmatic mount" action, and unmounting via "borgmatic umount" (#123).
This commit is contained in:
parent
f4a231420f
commit
375036e409
13 changed files with 436 additions and 6 deletions
5
NEWS
5
NEWS
|
@ -1,3 +1,8 @@
|
|||
1.4.15
|
||||
* #123: Support for mounting an archive as a FUSE filesystem via "borgmatic mount" action, and
|
||||
unmounting via "borgmatic umount". See the documentation for more information:
|
||||
https://torsion.org/borgmatic/docs/how-to/extract-a-backup/#mount-a-filesystem
|
||||
|
||||
1.4.14
|
||||
* Show summary log errors regardless of verbosity level, and log the "summary:" header with a log
|
||||
level based on the contained summary logs.
|
||||
|
|
46
borgmatic/borg/mount.py
Normal file
46
borgmatic/borg/mount.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
import logging
|
||||
|
||||
from borgmatic.execute import execute_command, execute_command_without_capture
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def mount_archive(
|
||||
repository,
|
||||
archive,
|
||||
mount_point,
|
||||
paths,
|
||||
foreground,
|
||||
options,
|
||||
storage_config,
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
):
|
||||
'''
|
||||
Given a local or remote repository path, an archive name, a filesystem mount point, zero or more
|
||||
paths to mount from the archive, extra Borg mount options, a storage configuration dict, and
|
||||
optional local and remote Borg paths, mount the archive onto the mount point.
|
||||
'''
|
||||
umask = storage_config.get('umask', None)
|
||||
lock_wait = storage_config.get('lock_wait', None)
|
||||
|
||||
full_command = (
|
||||
(local_path, 'mount')
|
||||
+ (('--remote-path', remote_path) if remote_path else ())
|
||||
+ (('--umask', str(umask)) if umask else ())
|
||||
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
||||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
|
||||
+ (('--foreground',) if foreground else ())
|
||||
+ (('-o', options) if options else ())
|
||||
+ ('::'.join((repository, archive)),)
|
||||
+ (mount_point,)
|
||||
+ (tuple(paths) if paths else ())
|
||||
)
|
||||
|
||||
# Don't capture the output when foreground mode is used so that ctrl-C can work properly.
|
||||
if foreground:
|
||||
execute_command_without_capture(full_command)
|
||||
return
|
||||
|
||||
execute_command(full_command)
|
20
borgmatic/borg/umount.py
Normal file
20
borgmatic/borg/umount.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import logging
|
||||
|
||||
from borgmatic.execute import execute_command
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def unmount_archive(mount_point, local_path='borg'):
|
||||
'''
|
||||
Given a mounted filesystem mount point, and an optional local Borg paths, umount the filesystem
|
||||
from the mount point.
|
||||
'''
|
||||
full_command = (
|
||||
(local_path, 'umount')
|
||||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
|
||||
+ (mount_point,)
|
||||
)
|
||||
|
||||
execute_command(full_command, error_on_warnings=True)
|
|
@ -9,6 +9,8 @@ SUBPARSER_ALIASES = {
|
|||
'create': ['--create', '-C'],
|
||||
'check': ['--check', '-k'],
|
||||
'extract': ['--extract', '-x'],
|
||||
'mount': ['--mount', '-m'],
|
||||
'umount': ['--umount', '-u'],
|
||||
'restore': ['--restore', '-r'],
|
||||
'list': ['--list', '-l'],
|
||||
'info': ['--info', '-i'],
|
||||
|
@ -312,6 +314,60 @@ def parse_arguments(*unparsed_arguments):
|
|||
'-h', '--help', action='help', help='Show this help message and exit'
|
||||
)
|
||||
|
||||
mount_parser = subparsers.add_parser(
|
||||
'mount',
|
||||
aliases=SUBPARSER_ALIASES['mount'],
|
||||
help='Mount files from a named archive as a FUSE filesystem',
|
||||
description='Mount a named archive as a FUSE filesystem',
|
||||
add_help=False,
|
||||
)
|
||||
mount_group = mount_parser.add_argument_group('mount arguments')
|
||||
mount_group.add_argument(
|
||||
'--repository',
|
||||
help='Path of repository to use, defaults to the configured repository if there is only one',
|
||||
)
|
||||
mount_group.add_argument('--archive', help='Name of archive to mount', required=True)
|
||||
mount_group.add_argument(
|
||||
'--mount-point',
|
||||
metavar='PATH',
|
||||
dest='mount_point',
|
||||
help='Path where filesystem is to be mounted',
|
||||
required=True,
|
||||
)
|
||||
mount_group.add_argument(
|
||||
'--path',
|
||||
metavar='PATH',
|
||||
nargs='+',
|
||||
dest='paths',
|
||||
help='Paths to mount from archive, defaults to the entire archive',
|
||||
)
|
||||
mount_group.add_argument(
|
||||
'--foreground',
|
||||
dest='foreground',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Stay in foreground until ctrl-C is pressed',
|
||||
)
|
||||
mount_group.add_argument('--options', dest='options', help='Extra Borg mount options')
|
||||
mount_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
|
||||
|
||||
umount_parser = subparsers.add_parser(
|
||||
'umount',
|
||||
aliases=SUBPARSER_ALIASES['umount'],
|
||||
help='Unmount a FUSE filesystem that was mounted with "borgmatic mount"',
|
||||
description='Unmount a mounted FUSE filesystem',
|
||||
add_help=False,
|
||||
)
|
||||
umount_group = umount_parser.add_argument_group('umount arguments')
|
||||
umount_group.add_argument(
|
||||
'--mount-point',
|
||||
metavar='PATH',
|
||||
dest='mount_point',
|
||||
help='Path of filesystem to unmount',
|
||||
required=True,
|
||||
)
|
||||
umount_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
|
||||
|
||||
restore_parser = subparsers.add_parser(
|
||||
'restore',
|
||||
aliases=SUBPARSER_ALIASES['restore'],
|
||||
|
|
|
@ -15,7 +15,9 @@ from borgmatic.borg import extract as borg_extract
|
|||
from borgmatic.borg import info as borg_info
|
||||
from borgmatic.borg import init as borg_init
|
||||
from borgmatic.borg import list as borg_list
|
||||
from borgmatic.borg import mount as borg_mount
|
||||
from borgmatic.borg import prune as borg_prune
|
||||
from borgmatic.borg import umount as borg_umount
|
||||
from borgmatic.commands.arguments import parse_arguments
|
||||
from borgmatic.config import checks, collect, convert, validate
|
||||
from borgmatic.hooks import command, dispatch, dump, monitor
|
||||
|
@ -246,6 +248,27 @@ def run_actions(
|
|||
destination_path=arguments['extract'].destination,
|
||||
progress=arguments['extract'].progress,
|
||||
)
|
||||
if 'mount' in arguments:
|
||||
if arguments['mount'].repository is None or repository == arguments['mount'].repository:
|
||||
logger.info('{}: Mounting archive {}'.format(repository, arguments['mount'].archive))
|
||||
borg_mount.mount_archive(
|
||||
repository,
|
||||
arguments['mount'].archive,
|
||||
arguments['mount'].mount_point,
|
||||
arguments['mount'].paths,
|
||||
arguments['mount'].foreground,
|
||||
arguments['mount'].options,
|
||||
storage,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
)
|
||||
if 'umount' in arguments:
|
||||
logger.info(
|
||||
'{}: Unmounting mount point {}'.format(repository, arguments['umount'].mount_point)
|
||||
)
|
||||
borg_umount.unmount_archive(
|
||||
mount_point=arguments['umount'].mount_point, local_path=local_path
|
||||
)
|
||||
if 'restore' in arguments:
|
||||
if arguments['restore'].repository is None or repository == arguments['restore'].repository:
|
||||
logger.info(
|
||||
|
@ -421,6 +444,8 @@ def collect_configuration_run_summary_logs(configs, arguments):
|
|||
repository = arguments['extract'].repository
|
||||
elif 'list' in arguments and arguments['list'].archive:
|
||||
repository = arguments['list'].repository
|
||||
elif 'mount' in arguments:
|
||||
repository = arguments['mount'].repository
|
||||
else:
|
||||
repository = None
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ FROM python:3.7.4-alpine3.10 as borgmatic
|
|||
COPY . /app
|
||||
RUN pip install --no-cache /app && generate-borgmatic-config && chmod +r /etc/borgmatic/config.yaml
|
||||
RUN borgmatic --help > /command-line.txt \
|
||||
&& for action in init prune create check extract restore list info; do \
|
||||
&& for action in init prune create check extract mount umount restore list info; do \
|
||||
echo -e "\n--------------------------------------------------------------------------------\n" >> /command-line.txt \
|
||||
&& borgmatic "$action" --help >> /command-line.txt; done
|
||||
|
||||
|
|
|
@ -87,6 +87,35 @@ so that you can extract files from your archive without impacting your live
|
|||
databases.
|
||||
|
||||
|
||||
## Mount a filesystem
|
||||
|
||||
If instead of extracting files, you'd like to explore the files from an
|
||||
archive as a [FUSE](https://en.wikipedia.org/wiki/Filesystem_in_Userspace)
|
||||
filesystem, you can use the `borgmatic mount` action. Here's an example:
|
||||
|
||||
```bash
|
||||
borgmatic mount --archive host-2019-... --mount-point /mnt
|
||||
```
|
||||
|
||||
This mounts the entire archive on the given mount point `/mnt`, so that you
|
||||
can look in there for your files.
|
||||
|
||||
If you'd like to restrict the mounted filesystem to only particular paths from
|
||||
your archive, use the `--path` flag, similar to the `extract` action above.
|
||||
For instance:
|
||||
|
||||
```bash
|
||||
borgmatic mount --archive host-2019-... --mount-point /mnt --path var/lib
|
||||
```
|
||||
|
||||
When you're all done exploring your files, unmount your mount point. No
|
||||
`--archive` flag is needed:
|
||||
|
||||
```bash
|
||||
borgmatic umount --mount-point /mnt
|
||||
```
|
||||
|
||||
|
||||
## Related documentation
|
||||
|
||||
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
|
||||
|
|
2
setup.py
2
setup.py
|
@ -1,6 +1,6 @@
|
|||
from setuptools import find_packages, setup
|
||||
|
||||
VERSION = '1.4.14'
|
||||
VERSION = '1.4.15'
|
||||
|
||||
|
||||
setup(
|
||||
|
|
|
@ -256,7 +256,7 @@ def test_parse_arguments_disallows_glob_archives_with_successful():
|
|||
)
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_repository_without_extract_or_list():
|
||||
def test_parse_arguments_disallows_repository_unless_action_consumes_it():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
|
@ -271,20 +271,36 @@ def test_parse_arguments_allows_repository_with_extract():
|
|||
)
|
||||
|
||||
|
||||
def test_parse_arguments_allows_repository_with_mount():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments(
|
||||
'--config',
|
||||
'myconfig',
|
||||
'mount',
|
||||
'--repository',
|
||||
'test.borg',
|
||||
'--archive',
|
||||
'test',
|
||||
'--mount-point',
|
||||
'/mnt',
|
||||
)
|
||||
|
||||
|
||||
def test_parse_arguments_allows_repository_with_list():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--config', 'myconfig', 'list', '--repository', 'test.borg')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_archive_without_extract_restore_or_list():
|
||||
def test_parse_arguments_disallows_archive_unless_action_consumes_it():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', '--archive', 'test')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_paths_without_extract():
|
||||
def test_parse_arguments_disallows_paths_unless_action_consumes_it():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
|
@ -297,6 +313,14 @@ def test_parse_arguments_allows_archive_with_extract():
|
|||
module.parse_arguments('--config', 'myconfig', 'extract', '--archive', 'test')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_archive_with_mount():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments(
|
||||
'--config', 'myconfig', 'mount', '--archive', 'test', '--mount-point', '/mnt'
|
||||
)
|
||||
|
||||
|
||||
def test_parse_arguments_allows_archive_with_dashed_extract():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
|
@ -328,6 +352,13 @@ def test_parse_arguments_requires_archive_with_extract():
|
|||
module.parse_arguments('--config', 'myconfig', 'extract')
|
||||
|
||||
|
||||
def test_parse_arguments_requires_archive_with_mount():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', 'mount', '--mount-point', '/mnt')
|
||||
|
||||
|
||||
def test_parse_arguments_requires_archive_with_restore():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
|
@ -335,6 +366,20 @@ def test_parse_arguments_requires_archive_with_restore():
|
|||
module.parse_arguments('--config', 'myconfig', 'restore')
|
||||
|
||||
|
||||
def test_parse_arguments_requires_mount_point_with_mount():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', 'mount', '--archive', 'test')
|
||||
|
||||
|
||||
def test_parse_arguments_requires_mount_point_with_umount():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', 'umount')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_progress_before_create():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ def test_extract_last_archive_dry_run_calls_borg_with_lock_wait_parameters():
|
|||
module.extract_last_archive_dry_run(repository='repo', lock_wait=5)
|
||||
|
||||
|
||||
def test_extract_archive_calls_borg_with_restore_path_parameters():
|
||||
def test_extract_archive_calls_borg_with_path_parameters():
|
||||
flexmock(module.os.path).should_receive('abspath').and_return('repo')
|
||||
insert_execute_command_mock(('borg', 'extract', 'repo::archive', 'path1', 'path2'))
|
||||
|
||||
|
|
144
tests/unit/borg/test_mount.py
Normal file
144
tests/unit/borg/test_mount.py
Normal file
|
@ -0,0 +1,144 @@
|
|||
import logging
|
||||
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.borg import mount as module
|
||||
|
||||
from ..test_verbosity import insert_logging_mock
|
||||
|
||||
|
||||
def insert_execute_command_mock(command):
|
||||
flexmock(module).should_receive('execute_command').with_args(command).once()
|
||||
|
||||
|
||||
def test_mount_archive_calls_borg_with_required_parameters():
|
||||
insert_execute_command_mock(('borg', 'mount', 'repo::archive', '/mnt'))
|
||||
|
||||
module.mount_archive(
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
mount_point='/mnt',
|
||||
paths=None,
|
||||
foreground=False,
|
||||
options=None,
|
||||
storage_config={},
|
||||
)
|
||||
|
||||
|
||||
def test_mount_archive_calls_borg_with_path_parameters():
|
||||
insert_execute_command_mock(('borg', 'mount', 'repo::archive', '/mnt', 'path1', 'path2'))
|
||||
|
||||
module.mount_archive(
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
mount_point='/mnt',
|
||||
paths=['path1', 'path2'],
|
||||
foreground=False,
|
||||
options=None,
|
||||
storage_config={},
|
||||
)
|
||||
|
||||
|
||||
def test_mount_archive_calls_borg_with_remote_path_parameters():
|
||||
insert_execute_command_mock(
|
||||
('borg', 'mount', '--remote-path', 'borg1', 'repo::archive', '/mnt')
|
||||
)
|
||||
|
||||
module.mount_archive(
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
mount_point='/mnt',
|
||||
paths=None,
|
||||
foreground=False,
|
||||
options=None,
|
||||
storage_config={},
|
||||
remote_path='borg1',
|
||||
)
|
||||
|
||||
|
||||
def test_mount_archive_calls_borg_with_umask_parameters():
|
||||
insert_execute_command_mock(('borg', 'mount', '--umask', '0770', 'repo::archive', '/mnt'))
|
||||
|
||||
module.mount_archive(
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
mount_point='/mnt',
|
||||
paths=None,
|
||||
foreground=False,
|
||||
options=None,
|
||||
storage_config={'umask': '0770'},
|
||||
)
|
||||
|
||||
|
||||
def test_mount_archive_calls_borg_with_lock_wait_parameters():
|
||||
insert_execute_command_mock(('borg', 'mount', '--lock-wait', '5', 'repo::archive', '/mnt'))
|
||||
|
||||
module.mount_archive(
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
mount_point='/mnt',
|
||||
paths=None,
|
||||
foreground=False,
|
||||
options=None,
|
||||
storage_config={'lock_wait': '5'},
|
||||
)
|
||||
|
||||
|
||||
def test_mount_archive_with_log_info_calls_borg_with_info_parameter():
|
||||
insert_execute_command_mock(('borg', 'mount', '--info', 'repo::archive', '/mnt'))
|
||||
insert_logging_mock(logging.INFO)
|
||||
|
||||
module.mount_archive(
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
mount_point='/mnt',
|
||||
paths=None,
|
||||
foreground=False,
|
||||
options=None,
|
||||
storage_config={},
|
||||
)
|
||||
|
||||
|
||||
def test_mount_archive_with_log_debug_calls_borg_with_debug_parameters():
|
||||
insert_execute_command_mock(('borg', 'mount', '--debug', '--show-rc', 'repo::archive', '/mnt'))
|
||||
insert_logging_mock(logging.DEBUG)
|
||||
|
||||
module.mount_archive(
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
mount_point='/mnt',
|
||||
paths=None,
|
||||
foreground=False,
|
||||
options=None,
|
||||
storage_config={},
|
||||
)
|
||||
|
||||
|
||||
def test_mount_archive_calls_borg_with_foreground_parameter():
|
||||
flexmock(module).should_receive('execute_command_without_capture').with_args(
|
||||
('borg', 'mount', '--foreground', 'repo::archive', '/mnt')
|
||||
).once()
|
||||
|
||||
module.mount_archive(
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
mount_point='/mnt',
|
||||
paths=None,
|
||||
foreground=True,
|
||||
options=None,
|
||||
storage_config={},
|
||||
)
|
||||
|
||||
|
||||
def test_mount_archive_calls_borg_with_options_parameters():
|
||||
insert_execute_command_mock(('borg', 'mount', '-o', 'super_mount', 'repo::archive', '/mnt'))
|
||||
|
||||
module.mount_archive(
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
mount_point='/mnt',
|
||||
paths=None,
|
||||
foreground=False,
|
||||
options='super_mount',
|
||||
storage_config={},
|
||||
)
|
33
tests/unit/borg/test_umount.py
Normal file
33
tests/unit/borg/test_umount.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
import logging
|
||||
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.borg import umount as module
|
||||
|
||||
from ..test_verbosity import insert_logging_mock
|
||||
|
||||
|
||||
def insert_execute_command_mock(command):
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
command, error_on_warnings=True
|
||||
).once()
|
||||
|
||||
|
||||
def test_unmount_archive_calls_borg_with_required_parameters():
|
||||
insert_execute_command_mock(('borg', 'umount', '/mnt'))
|
||||
|
||||
module.unmount_archive(mount_point='/mnt')
|
||||
|
||||
|
||||
def test_unmount_archive_with_log_info_calls_borg_with_info_parameter():
|
||||
insert_execute_command_mock(('borg', 'umount', '--info', '/mnt'))
|
||||
insert_logging_mock(logging.INFO)
|
||||
|
||||
module.unmount_archive(mount_point='/mnt')
|
||||
|
||||
|
||||
def test_unmount_archive_with_log_debug_calls_borg_with_debug_parameters():
|
||||
insert_execute_command_mock(('borg', 'umount', '--debug', '--show-rc', '/mnt'))
|
||||
insert_logging_mock(logging.DEBUG)
|
||||
|
||||
module.unmount_archive(mount_point='/mnt')
|
|
@ -219,6 +219,33 @@ def test_collect_configuration_run_summary_logs_extract_with_repository_error():
|
|||
assert logs == expected_logs
|
||||
|
||||
|
||||
def test_collect_configuration_run_summary_logs_info_for_success_with_mount():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
arguments = {'mount': flexmock(repository='repo')}
|
||||
|
||||
logs = tuple(
|
||||
module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||
)
|
||||
|
||||
assert {log.levelno for log in logs} == {logging.INFO}
|
||||
|
||||
|
||||
def test_collect_configuration_run_summary_logs_mount_with_repository_error():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository').and_raise(
|
||||
ValueError
|
||||
)
|
||||
expected_logs = (flexmock(),)
|
||||
flexmock(module).should_receive('make_error_log_records').and_return(expected_logs)
|
||||
arguments = {'mount': flexmock(repository='repo')}
|
||||
|
||||
logs = tuple(
|
||||
module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||
)
|
||||
|
||||
assert logs == expected_logs
|
||||
|
||||
|
||||
def test_collect_configuration_run_summary_logs_missing_configs_error():
|
||||
arguments = {'global': flexmock(config_paths=[])}
|
||||
expected_logs = (flexmock(),)
|
||||
|
|
Loading…
Reference in a new issue