diff --git a/borgmatic/borg/create.py b/borgmatic/borg/create.py index 0889c3d1..87a0fdd7 100644 --- a/borgmatic/borg/create.py +++ b/borgmatic/borg/create.py @@ -306,6 +306,20 @@ def collect_special_file_paths( ) +def check_all_source_directories_exist(source_directories): + ''' + Given a sequence of source directories, check that they all exist. If any do not, raise an + exception. + ''' + missing_directories = [ + source_directory + for source_directory in source_directories + if not os.path.exists(source_directory) + ] + if missing_directories: + raise ValueError(f"Source directories do not exist: {', '.join(missing_directories)}") + + def create_archive( dry_run, repository, @@ -331,6 +345,8 @@ def create_archive( borgmatic_source_directories = expand_directories( collect_borgmatic_source_directories(location_config.get('borgmatic_source_directory')) ) + if location_config.get('source_directories_must_exist', False): + check_all_source_directories_exist(location_config.get('source_directories')) sources = deduplicate_directories( map_directories_to_devices( expand_directories( diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 2cb6dbb3..d4d57ab6 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -202,6 +202,12 @@ properties: path prevents "borgmatic restore" from finding any database dumps created before the change. Defaults to ~/.borgmatic example: /tmp/borgmatic + source_directories_must_exist: + type: boolean + description: | + If true, then source directories must exist, otherwise an + error is raised. Defaults to false. + example: true storage: type: object description: | diff --git a/tests/unit/borg/test_create.py b/tests/unit/borg/test_create.py index 10b5a951..04afa872 100644 --- a/tests/unit/borg/test_create.py +++ b/tests/unit/borg/test_create.py @@ -207,7 +207,6 @@ def test_make_exclude_flags_includes_exclude_patterns_filename_when_given(): def test_make_exclude_flags_includes_exclude_from_filenames_when_in_config(): - exclude_flags = module.make_exclude_flags( location_config={'exclude_from': ['excludes', 'other']} ) @@ -2530,3 +2529,27 @@ def test_create_archive_with_stream_processes_calls_borg_with_processes_and_read local_borg_version='1.2.3', stream_processes=processes, ) + + +def test_create_archive_with_non_existent_directory_and_source_directories_must_exist_raises_error(): + """ + If a source directory doesn't exist and source_directories_must_exist is True, raise an error. + """ + flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels') + flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER + flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([]) + flexmock(module.os.path).should_receive('exists').and_return(False) + + with pytest.raises(ValueError): + module.create_archive( + dry_run=False, + repository='repo', + location_config={ + 'source_directories': ['foo', 'bar'], + 'repositories': ['repo'], + 'exclude_patterns': None, + 'source_directories_must_exist': True, + }, + storage_config={}, + local_borg_version='1.2.3', + )