Browse Source

Account for the case where "store_config_files: false" and the runtime directory never gets added to patterns to begin with (#1122).

Dan Helfman 1 week ago
parent
commit
c4c40af812
2 changed files with 91 additions and 29 deletions
  1. 26 13
      borgmatic/borg/create.py
  2. 65 16
      tests/unit/borg/test_create.py

+ 26 - 13
borgmatic/borg/create.py

@@ -49,6 +49,7 @@ def validate_planned_backup_paths(
     dry_run,
     create_command,
     config,
+    patterns,
     local_path,
     working_directory,
     borgmatic_runtime_directory,
@@ -91,21 +92,32 @@ def validate_planned_backup_paths(
         if path_line and path_line.startswith(('- ', '+ '))
     )
 
-    # These are the subset of those files that contain the borgmatic runtime directory.
-    paths_containing_runtime_directory = {}
-
-    if os.path.exists(borgmatic_runtime_directory):
-        paths_containing_runtime_directory = {
-            path for path in paths if any_parent_directories(path, (borgmatic_runtime_directory,))
-        }
+    # These are the subset of output paths contained within the borgmatic runtime directory.
+    paths_inside_runtime_directory = {
+        path for path in paths if any_parent_directories(path, (borgmatic_runtime_directory,))
+    }
+
+    # If the runtime directory isn't present in the source patterns, then we shouldn't expect it to
+    # be in the paths output from the Borg dry run.
+    runtime_directory_present_in_patterns = any(
+        pattern
+        for pattern in patterns
+        if any_parent_directories(pattern.path, (borgmatic_runtime_directory,))
+        if pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
+    )
 
-        # If no paths to backup contain the runtime directory, it must've been excluded.
-        if not paths_containing_runtime_directory and not dry_run:
-            raise ValueError(
-                f'The runtime directory {os.path.normpath(borgmatic_runtime_directory)} overlaps with the configured excludes or patterns with excludes. Please ensure the runtime directory is not excluded.',
-            )
+    # If no paths to backup are inside the runtime directory, it must've been excluded.
+    if (
+        not paths_inside_runtime_directory
+        and runtime_directory_present_in_patterns
+        and not dry_run
+        and os.path.exists(borgmatic_runtime_directory)
+    ):
+        raise ValueError(
+            f'The runtime directory {os.path.normpath(borgmatic_runtime_directory)} overlaps with the configured excludes or patterns with excludes. Please ensure the runtime directory is not excluded.',
+        )
 
-    return tuple(path for path in paths if path not in paths_containing_runtime_directory)
+    return tuple(path for path in paths if path not in paths_inside_runtime_directory)
 
 
 MAX_SPECIAL_FILE_PATHS_LENGTH = 1000
@@ -227,6 +239,7 @@ def make_base_create_command(
         dry_run,
         create_flags + create_positional_arguments,
         config,
+        patterns,
         local_path,
         working_directory,
         borgmatic_runtime_directory=borgmatic_runtime_directory,

+ 65 - 16
tests/unit/borg/test_create.py

@@ -71,12 +71,17 @@ def test_validate_planned_backup_paths_parses_borg_dry_run_file_list():
         'Processing files ...\n- /foo\n+ /bar\n- /baz',
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
-    flexmock(module).should_receive('any_parent_directories').never()
+    flexmock(module).should_receive('any_parent_directories').and_return(False)
 
     assert module.validate_planned_backup_paths(
         dry_run=False,
         create_command=('borg', 'create'),
         config={},
+        patterns=(
+            module.borgmatic.borg.pattern.Pattern('/foo'),
+            module.borgmatic.borg.pattern.Pattern('/bar'),
+            module.borgmatic.borg.pattern.Pattern('/baz'),
+        ),
         local_path=None,
         working_directory=None,
         borgmatic_runtime_directory='/run/borgmatic',
@@ -95,23 +100,21 @@ def test_validate_planned_backup_paths_skips_borgmatic_runtime_directory():
         '+ /foo\n- /run/borgmatic/bar\n- /baz',
     )
     flexmock(module.os.path).should_receive('exists').and_return(True)
-    flexmock(module).should_receive('any_parent_directories').with_args(
-        '/foo',
-        ('/run/borgmatic',),
-    ).and_return(False)
-    flexmock(module).should_receive('any_parent_directories').with_args(
-        '/run/borgmatic/bar',
-        ('/run/borgmatic',),
-    ).and_return(True)
-    flexmock(module).should_receive('any_parent_directories').with_args(
-        '/baz',
-        ('/run/borgmatic',),
-    ).and_return(False)
+    flexmock(module).should_receive('any_parent_directories').replace_with(
+        lambda path, _: path == '/run/borgmatic/bar'
+    )
 
     assert module.validate_planned_backup_paths(
         dry_run=False,
         create_command=('borg', 'create'),
         config={},
+        patterns=(
+            module.borgmatic.borg.pattern.Pattern('/foo'),
+            module.borgmatic.borg.pattern.Pattern(
+                '/run/borgmatic/bar', module.borgmatic.borg.pattern.Pattern_type.ROOT
+            ),
+            module.borgmatic.borg.pattern.Pattern('/baz'),
+        ),
         local_path=None,
         working_directory=None,
         borgmatic_runtime_directory='/run/borgmatic',
@@ -130,19 +133,58 @@ def test_validate_planned_backup_paths_with_borgmatic_runtime_directory_missing_
         '+ /foo\n- /bar\n- /baz',
     )
     flexmock(module.os.path).should_receive('exists').and_return(True)
-    flexmock(module).should_receive('any_parent_directories').and_return(False)
+    flexmock(module).should_receive('any_parent_directories').replace_with(
+        lambda path, _: path == '/run/borgmatic/bar'
+    )
 
     with pytest.raises(ValueError):
         module.validate_planned_backup_paths(
             dry_run=False,
             create_command=('borg', 'create'),
             config={},
+            patterns=(
+                module.borgmatic.borg.pattern.Pattern('/foo'),
+                module.borgmatic.borg.pattern.Pattern(
+                    '/run/borgmatic/bar', module.borgmatic.borg.pattern.Pattern_type.ROOT
+                ),
+                module.borgmatic.borg.pattern.Pattern('/baz'),
+            ),
             local_path=None,
             working_directory=None,
             borgmatic_runtime_directory='/run/borgmatic',
         )
 
 
+def test_validate_planned_backup_paths_with_borgmatic_runtime_directory_missing_from_patterns_does_not_raise():
+    flexmock(module.flags).should_receive('omit_flag').replace_with(
+        lambda arguments, flag: arguments,
+    )
+    flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
+        lambda arguments, flag: arguments,
+    )
+    flexmock(module.environment).should_receive('make_environment').and_return(None)
+    flexmock(module).should_receive('execute_command_and_capture_output').and_return(
+        '+ /foo\n- /run/borgmatic/bar\n- /baz',
+    )
+    flexmock(module.os.path).should_receive('exists').and_return(True)
+    flexmock(module).should_receive('any_parent_directories').replace_with(
+        lambda path, _: path == '/run/borgmatic/bar'
+    )
+
+    assert module.validate_planned_backup_paths(
+        dry_run=False,
+        create_command=('borg', 'create'),
+        config={},
+        patterns=(
+            module.borgmatic.borg.pattern.Pattern('/foo'),
+            module.borgmatic.borg.pattern.Pattern('/baz'),
+        ),
+        local_path=None,
+        working_directory=None,
+        borgmatic_runtime_directory='/run/borgmatic',
+    ) == ('/foo', '/baz')
+
+
 def test_validate_planned_backup_paths_with_dry_run_and_borgmatic_runtime_directory_missing_from_paths_output_does_not_raise():
     flexmock(module.flags).should_receive('omit_flag').replace_with(
         lambda arguments, flag: arguments,
@@ -152,7 +194,7 @@ def test_validate_planned_backup_paths_with_dry_run_and_borgmatic_runtime_direct
     )
     flexmock(module.environment).should_receive('make_environment').and_return(None)
     flexmock(module).should_receive('execute_command_and_capture_output').and_return(
-        '+ /foo\n- /bar\n- /baz',
+        '+ /foo\n- /run/borgmatic/bar\n- /baz',
     )
     flexmock(module.os.path).should_receive('exists').and_return(True)
     flexmock(module).should_receive('any_parent_directories').and_return(False)
@@ -161,10 +203,17 @@ def test_validate_planned_backup_paths_with_dry_run_and_borgmatic_runtime_direct
         dry_run=True,
         create_command=('borg', 'create'),
         config={},
+        patterns=(
+            module.borgmatic.borg.pattern.Pattern('/foo'),
+            module.borgmatic.borg.pattern.Pattern(
+                '/run/borgmatic/bar', module.borgmatic.borg.pattern.Pattern_type.ROOT
+            ),
+            module.borgmatic.borg.pattern.Pattern('/baz'),
+        ),
         local_path=None,
         working_directory=None,
         borgmatic_runtime_directory='/run/borgmatic',
-    ) == ('/foo', '/bar', '/baz')
+    ) == ('/foo', '/run/borgmatic/bar', '/baz')
 
 
 DEFAULT_ARCHIVE_NAME = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'