Merge pull request #1636 from textshell/feature/subcommands

Use subcommands for debug-* and key-*
This commit is contained in:
TW 2016-09-25 23:33:38 +02:00 committed by GitHub
commit 38819552df
4 changed files with 124 additions and 18 deletions

View file

@ -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):

View file

@ -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'

View file

@ -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

View file

@ -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):