소스 검색

implement special tags, see #953

special tags start with @ and have clobber protection,
so users can't accidentally remove them using borg tag --set.
it is possible though to still use --set, but one must also
give all special tags that the archive(s) already have.

there is only a known set of allowed special tags:
@PROT - protects archives against archive pruning or archive deletion
setting unknown tags beginning with @ is disallowed.
Thomas Waldmann 8 달 전
부모
커밋
bfd316694d
3개의 변경된 파일62개의 추가작업 그리고 2개의 파일을 삭제
  1. 27 2
      src/borg/archiver/tag_cmd.py
  2. 4 0
      src/borg/constants.py
  3. 31 0
      src/borg/testsuite/archiver/tag_cmd_test.py

+ 27 - 2
src/borg/archiver/tag_cmd.py

@@ -3,7 +3,7 @@ import argparse
 from ._common import with_repository, define_archive_filters_group
 from ..archive import Archive
 from ..constants import *  # NOQA
-from ..helpers import bin_to_hex, archivename_validator, tag_validator
+from ..helpers import bin_to_hex, archivename_validator, tag_validator, Error
 from ..manifest import Manifest
 
 from ..logger import create_logger
@@ -25,10 +25,26 @@ class TagMixIn:
         else:
             archive_infos = manifest.archives.list_considering(args)
 
+        def check_special(tags):
+            if tags:
+                special = {tag for tag in tags_set(tags) if tag.startswith("@")}
+                if not special.issubset(SPECIAL_TAGS):
+                    raise Error("unknown special tags given.")
+
+        check_special(args.set_tags)
+        check_special(args.add_tags)
+        check_special(args.remove_tags)
+
         for archive_info in archive_infos:
             archive = Archive(manifest, archive_info.id, cache=cache)
             if args.set_tags:
-                archive.tags = tags_set(args.set_tags)
+                # avoid that --set (accidentally) erases existing special tags,
+                # but allow --set if the existing special tags are also given.
+                new_tags = tags_set(args.set_tags)
+                existing_special = {tag for tag in archive.tags if tag.startswith("@")}
+                clobber = not existing_special.issubset(new_tags)
+                if not clobber:
+                    archive.tags = new_tags
             if args.add_tags:
                 archive.tags |= tags_set(args.add_tags)
             if args.remove_tags:
@@ -53,6 +69,15 @@ class TagMixIn:
 
             You can set the tags to a specific set of tags or you can add or remove
             tags from the current set of tags.
+
+            User defined tags must not start with `@` because such tags are considered
+            special and users are only allowed to use known special tags:
+
+            ``@PROT``: protects archives against archive deletion or pruning.
+
+            Pre-existing special tags can not be removed via ``--set``. You can still use
+            ``--set``, but you must give pre-existing special tags also (so they won't be
+            removed).
             """
         )
         subparser = subparsers.add_parser(

+ 4 - 0
src/borg/constants.py

@@ -124,6 +124,10 @@ TIME_DIFFERS2_NS = 3000000000
 # tar related
 SCHILY_XATTR = "SCHILY.xattr."  # xattr key prefix in tar PAX headers
 
+# special tags
+# @PROT protects archives against accidential deletion or modification by delete, prune or recreate.
+SPECIAL_TAGS = frozenset(["@PROT"])
+
 # return codes returned by borg command
 EXIT_SUCCESS = 0  # everything done, no problems
 EXIT_WARNING = 1  # reached normal end of operation, but there were issues (generic warning)

+ 31 - 0
src/borg/testsuite/archiver/tag_cmd_test.py

@@ -1,5 +1,8 @@
+import pytest
+
 from ...constants import *  # NOQA
 from . import cmd, generate_archiver_tests, RK_ENCRYPTION
+from ...helpers import Error
 
 pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local")  # NOQA
 
@@ -30,3 +33,31 @@ def test_tag_add_remove(archivers, request):
     assert "tags: bb." in output
     output = cmd(archiver, "tag", "-a", "archive", "--remove", "bb")
     assert "tags: ." in output
+
+
+def test_tag_set_noclobber_special(archivers, request):
+    archiver = request.getfixturevalue(archivers)
+    cmd(archiver, "repo-create", RK_ENCRYPTION)
+    cmd(archiver, "create", "archive", archiver.input_path)
+    output = cmd(archiver, "tag", "-a", "archive", "--set", "@PROT")
+    assert "tags: @PROT." in output
+    # archive now has a special tag.
+    # it must not be possible to accidentally erase such special tags by using --set:
+    output = cmd(archiver, "tag", "-a", "archive", "--set", "clobber")
+    assert "tags: @PROT." in output
+    # it is possible though to use --set if the existing special tags are also given:
+    output = cmd(archiver, "tag", "-a", "archive", "--set", "noclobber", "--set", "@PROT")
+    assert "tags: @PROT,noclobber." in output
+
+
+def test_tag_only_known_special(archivers, request):
+    archiver = request.getfixturevalue(archivers)
+    cmd(archiver, "repo-create", RK_ENCRYPTION)
+    cmd(archiver, "create", "archive", archiver.input_path)
+    # user can't set / add / remove unknown special tags
+    with pytest.raises(Error):
+        cmd(archiver, "tag", "-a", "archive", "--set", "@UNKNOWN")
+    with pytest.raises(Error):
+        cmd(archiver, "tag", "-a", "archive", "--add", "@UNKNOWN")
+    with pytest.raises(Error):
+        cmd(archiver, "tag", "-a", "archive", "--remove", "@UNKNOWN")