mirror of
https://github.com/borgbackup/borg.git
synced 2026-06-03 13:45:59 -04:00
Merge pull request #1636 from textshell/feature/subcommands
Use subcommands for debug-* and key-*
This commit is contained in:
commit
38819552df
4 changed files with 124 additions and 18 deletions
|
|
@ -1112,7 +1112,15 @@ class Archiver:
|
|||
subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
|
||||
type=location_validator(archive=False))
|
||||
|
||||
subparser = subparsers.add_parser('key-export', parents=[common_parser],
|
||||
subparser = subparsers.add_parser('key',
|
||||
description="Manage a keyfile or repokey of a repository",
|
||||
epilog="",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help='manage repository key')
|
||||
|
||||
key_parsers = subparser.add_subparsers(title='required arguments', metavar='<command>')
|
||||
|
||||
subparser = key_parsers.add_parser('export', parents=[common_parser],
|
||||
description=self.do_key_export.__doc__,
|
||||
epilog="",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
|
|
@ -1126,7 +1134,7 @@ class Archiver:
|
|||
default=False,
|
||||
help='Create an export suitable for printing and later type-in')
|
||||
|
||||
subparser = subparsers.add_parser('key-import', parents=[common_parser],
|
||||
subparser = key_parsers.add_parser('import', parents=[common_parser],
|
||||
description=self.do_key_import.__doc__,
|
||||
epilog="",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
|
|
@ -1579,6 +1587,22 @@ class Archiver:
|
|||
subparser.add_argument('topic', metavar='TOPIC', type=str, nargs='?',
|
||||
help='additional help on TOPIC')
|
||||
|
||||
debug_epilog = textwrap.dedent("""
|
||||
These commands are not intended for normal use and potentially very
|
||||
dangerous if used incorrectly.
|
||||
|
||||
They exist to improve debugging capabilities without direct system access, e.g.
|
||||
in case you ever run into some severe malfunction. Use them only if you know
|
||||
what you are doing or if a trusted developer tells you what to do.""")
|
||||
|
||||
subparser = subparsers.add_parser('debug',
|
||||
description='debugging command (not intended for normal use)',
|
||||
epilog=debug_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help='debugging command (not intended for normal use)')
|
||||
|
||||
debug_parsers = subparser.add_subparsers(title='required arguments', metavar='<command>')
|
||||
|
||||
debug_info_epilog = textwrap.dedent("""
|
||||
This command displays some system information that might be useful for bug
|
||||
reports and debugging problems. If a traceback happens, this information is
|
||||
|
|
@ -1591,6 +1615,13 @@ class Archiver:
|
|||
help='show system infos for debugging / bug reports (debug)')
|
||||
subparser.set_defaults(func=self.do_debug_info)
|
||||
|
||||
subparser = debug_parsers.add_parser('info', parents=[common_parser],
|
||||
description=self.do_debug_info.__doc__,
|
||||
epilog=debug_info_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help='show system infos for debugging / bug reports (debug)')
|
||||
subparser.set_defaults(func=self.do_debug_info)
|
||||
|
||||
debug_dump_archive_items_epilog = textwrap.dedent("""
|
||||
This command dumps raw (but decrypted and decompressed) archive items (only metadata) to files.
|
||||
""")
|
||||
|
|
@ -1604,6 +1635,16 @@ class Archiver:
|
|||
type=location_validator(archive=True),
|
||||
help='archive to dump')
|
||||
|
||||
subparser = debug_parsers.add_parser('dump-archive-items', parents=[common_parser],
|
||||
description=self.do_debug_dump_archive_items.__doc__,
|
||||
epilog=debug_dump_archive_items_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help='dump archive items (metadata) (debug)')
|
||||
subparser.set_defaults(func=self.do_debug_dump_archive_items)
|
||||
subparser.add_argument('location', metavar='ARCHIVE',
|
||||
type=location_validator(archive=True),
|
||||
help='archive to dump')
|
||||
|
||||
debug_dump_repo_objs_epilog = textwrap.dedent("""
|
||||
This command dumps raw (but decrypted and decompressed) repo objects to files.
|
||||
""")
|
||||
|
|
@ -1617,6 +1658,16 @@ class Archiver:
|
|||
type=location_validator(archive=False),
|
||||
help='repo to dump')
|
||||
|
||||
subparser = debug_parsers.add_parser('dump-repo-objs', parents=[common_parser],
|
||||
description=self.do_debug_dump_repo_objs.__doc__,
|
||||
epilog=debug_dump_repo_objs_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help='dump repo objects (debug)')
|
||||
subparser.set_defaults(func=self.do_debug_dump_repo_objs)
|
||||
subparser.add_argument('location', metavar='REPOSITORY',
|
||||
type=location_validator(archive=False),
|
||||
help='repo to dump')
|
||||
|
||||
debug_get_obj_epilog = textwrap.dedent("""
|
||||
This command gets an object from the repository.
|
||||
""")
|
||||
|
|
@ -1634,6 +1685,20 @@ class Archiver:
|
|||
subparser.add_argument('path', metavar='PATH', type=str,
|
||||
help='file to write object data into')
|
||||
|
||||
subparser = debug_parsers.add_parser('get-obj', parents=[common_parser],
|
||||
description=self.do_debug_get_obj.__doc__,
|
||||
epilog=debug_get_obj_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help='get object from repository (debug)')
|
||||
subparser.set_defaults(func=self.do_debug_get_obj)
|
||||
subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
|
||||
type=location_validator(archive=False),
|
||||
help='repository to use')
|
||||
subparser.add_argument('id', metavar='ID', type=str,
|
||||
help='hex object ID to get from the repo')
|
||||
subparser.add_argument('path', metavar='PATH', type=str,
|
||||
help='file to write object data into')
|
||||
|
||||
debug_put_obj_epilog = textwrap.dedent("""
|
||||
This command puts objects into the repository.
|
||||
""")
|
||||
|
|
@ -1649,6 +1714,18 @@ class Archiver:
|
|||
subparser.add_argument('paths', metavar='PATH', nargs='+', type=str,
|
||||
help='file(s) to read and create object(s) from')
|
||||
|
||||
subparser = debug_parsers.add_parser('put-obj', parents=[common_parser],
|
||||
description=self.do_debug_put_obj.__doc__,
|
||||
epilog=debug_put_obj_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help='put object to repository (debug)')
|
||||
subparser.set_defaults(func=self.do_debug_put_obj)
|
||||
subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
|
||||
type=location_validator(archive=False),
|
||||
help='repository to use')
|
||||
subparser.add_argument('paths', metavar='PATH', nargs='+', type=str,
|
||||
help='file(s) to read and create object(s) from')
|
||||
|
||||
debug_delete_obj_epilog = textwrap.dedent("""
|
||||
This command deletes objects from the repository.
|
||||
""")
|
||||
|
|
@ -1663,6 +1740,19 @@ class Archiver:
|
|||
help='repository to use')
|
||||
subparser.add_argument('ids', metavar='IDs', nargs='+', type=str,
|
||||
help='hex object ID(s) to delete from the repo')
|
||||
|
||||
subparser = debug_parsers.add_parser('delete-obj', parents=[common_parser],
|
||||
description=self.do_debug_delete_obj.__doc__,
|
||||
epilog=debug_delete_obj_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help='delete object from repository (debug)')
|
||||
subparser.set_defaults(func=self.do_debug_delete_obj)
|
||||
subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
|
||||
type=location_validator(archive=False),
|
||||
help='repository to use')
|
||||
subparser.add_argument('ids', metavar='IDs', nargs='+', type=str,
|
||||
help='hex object ID(s) to delete from the repo')
|
||||
|
||||
return parser
|
||||
|
||||
def get_args(self, argv, cmd):
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ class KeyManager:
|
|||
i += 1
|
||||
return ret
|
||||
|
||||
export = 'To restore key use borg key-import --paper /path/to/repo\n\n'
|
||||
export = 'To restore key use borg key import --paper /path/to/repo\n\n'
|
||||
|
||||
binary = a2b_base64(self.keyblob)
|
||||
export += 'BORG PAPER KEY v1\n'
|
||||
|
|
|
|||
|
|
@ -1200,7 +1200,7 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|||
export_file = self.output_path + '/exported'
|
||||
self.cmd('init', self.repository_location, '--encryption', 'keyfile')
|
||||
repo_id = self._extract_repository_id(self.repository_path)
|
||||
self.cmd('key-export', self.repository_location, export_file)
|
||||
self.cmd('key', 'export', self.repository_location, export_file)
|
||||
|
||||
with open(export_file, 'r') as fd:
|
||||
export_contents = fd.read()
|
||||
|
|
@ -1216,7 +1216,7 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|||
|
||||
os.unlink(key_file)
|
||||
|
||||
self.cmd('key-import', self.repository_location, export_file)
|
||||
self.cmd('key', 'import', self.repository_location, export_file)
|
||||
|
||||
with open(key_file, 'r') as fd:
|
||||
key_contents2 = fd.read()
|
||||
|
|
@ -1227,7 +1227,7 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|||
export_file = self.output_path + '/exported'
|
||||
self.cmd('init', self.repository_location, '--encryption', 'repokey')
|
||||
repo_id = self._extract_repository_id(self.repository_path)
|
||||
self.cmd('key-export', self.repository_location, export_file)
|
||||
self.cmd('key', 'export', self.repository_location, export_file)
|
||||
|
||||
with open(export_file, 'r') as fd:
|
||||
export_contents = fd.read()
|
||||
|
|
@ -1246,7 +1246,7 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|||
with Repository(self.repository_path) as repository:
|
||||
repository.save_key(b'')
|
||||
|
||||
self.cmd('key-import', self.repository_location, export_file)
|
||||
self.cmd('key', 'import', self.repository_location, export_file)
|
||||
|
||||
with Repository(self.repository_path) as repository:
|
||||
repo_key2 = RepoKey(repository)
|
||||
|
|
@ -1258,17 +1258,17 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|||
export_file = self.output_path + '/exported'
|
||||
self.cmd('init', self.repository_location, '--encryption', 'keyfile')
|
||||
|
||||
self.cmd('key-import', self.repository_location, export_file, exit_code=EXIT_ERROR)
|
||||
self.cmd('key', 'import', self.repository_location, export_file, exit_code=EXIT_ERROR)
|
||||
|
||||
with open(export_file, 'w') as fd:
|
||||
fd.write('something not a key\n')
|
||||
|
||||
self.assert_raises(NotABorgKeyFile, lambda: self.cmd('key-import', self.repository_location, export_file))
|
||||
self.assert_raises(NotABorgKeyFile, lambda: self.cmd('key', 'import', self.repository_location, export_file))
|
||||
|
||||
with open(export_file, 'w') as fd:
|
||||
fd.write('BORG_KEY a0a0a0\n')
|
||||
|
||||
self.assert_raises(RepoIdMismatch, lambda: self.cmd('key-import', self.repository_location, export_file))
|
||||
self.assert_raises(RepoIdMismatch, lambda: self.cmd('key', 'import', self.repository_location, export_file))
|
||||
|
||||
def test_key_export_paperkey(self):
|
||||
repo_id = 'e294423506da4e1ea76e8dcdf1a3919624ae3ae496fddf905610c351d3f09239'
|
||||
|
|
@ -1283,12 +1283,12 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|||
fd.write(KeyfileKey.FILE_ID + ' ' + repo_id + '\n')
|
||||
fd.write(b2a_base64(b'abcdefghijklmnopqrstu').decode())
|
||||
|
||||
self.cmd('key-export', '--paper', self.repository_location, export_file)
|
||||
self.cmd('key', 'export', '--paper', self.repository_location, export_file)
|
||||
|
||||
with open(export_file, 'r') as fd:
|
||||
export_contents = fd.read()
|
||||
|
||||
assert export_contents == """To restore key use borg key-import --paper /path/to/repo
|
||||
assert export_contents == """To restore key use borg key import --paper /path/to/repo
|
||||
|
||||
BORG PAPER KEY v1
|
||||
id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02
|
||||
|
|
|
|||
28
setup.py
28
setup.py
|
|
@ -150,19 +150,33 @@ class build_usage(Command):
|
|||
|
||||
def run(self):
|
||||
print('generating usage docs')
|
||||
if not os.path.exists('docs/usage'):
|
||||
os.mkdir('docs/usage')
|
||||
# allows us to build docs without the C modules fully loaded during help generation
|
||||
from borg.archiver import Archiver
|
||||
parser = Archiver().build_parser(prog='borg')
|
||||
|
||||
self.generate_level("", parser, Archiver)
|
||||
|
||||
def generate_level(self, prefix, parser, Archiver):
|
||||
is_subcommand = False
|
||||
choices = {}
|
||||
for action in parser._actions:
|
||||
if action.choices is not None:
|
||||
choices.update(action.choices)
|
||||
if action.choices is not None and 'SubParsersAction' in str(action.__class__):
|
||||
is_subcommand = True
|
||||
for cmd, parser in action.choices.items():
|
||||
choices[prefix + cmd] = parser
|
||||
if prefix and not choices:
|
||||
return
|
||||
print('found commands: %s' % list(choices.keys()))
|
||||
if not os.path.exists('docs/usage'):
|
||||
os.mkdir('docs/usage')
|
||||
|
||||
for command, parser in choices.items():
|
||||
print('generating help for %s' % command)
|
||||
with open('docs/usage/%s.rst.inc' % command, 'w') as doc:
|
||||
|
||||
if self.generate_level(command + " ", parser, Archiver):
|
||||
return
|
||||
|
||||
with open('docs/usage/%s.rst.inc' % command.replace(" ", "_"), 'w') as doc:
|
||||
doc.write(".. IMPORTANT: this file is auto-generated from borg's built-in help, do not edit!\n\n")
|
||||
if command == 'help':
|
||||
for topic in Archiver.helptext:
|
||||
|
|
@ -173,14 +187,16 @@ class build_usage(Command):
|
|||
doc.write(Archiver.helptext[topic])
|
||||
else:
|
||||
params = {"command": command,
|
||||
"command_": command.replace(' ', '_'),
|
||||
"underline": '-' * len('borg ' + command)}
|
||||
doc.write(".. _borg_{command}:\n\n".format(**params))
|
||||
doc.write(".. _borg_{command_}:\n\n".format(**params))
|
||||
doc.write("borg {command}\n{underline}\n::\n\n".format(**params))
|
||||
epilog = parser.epilog
|
||||
parser.epilog = None
|
||||
doc.write(re.sub("^", " ", parser.format_help(), flags=re.M))
|
||||
doc.write("\nDescription\n~~~~~~~~~~~\n")
|
||||
doc.write(epilog)
|
||||
return is_subcommand
|
||||
|
||||
|
||||
class build_api(Command):
|
||||
|
|
|
|||
Loading…
Reference in a new issue