From 77bab3a39296a36c1c4213306a22e219c21dcee0 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 12 Dec 2015 16:04:25 +0100 Subject: Add list of Obnam errors to manual This also drops the find-all-obnam-errors in favour of "obnam list-errors". --- find-all-obnam-errors | 92 ----------- manual/en/800-errors.mdwn | 10 ++ manual/en/801-errors.mdwn | 269 +++++++++++++++++++++++++++++++++ obnamlib/__init__.py | 1 + obnamlib/plugins/list_errors_plugin.py | 48 ++++++ obnamlib/structurederror_finder.py | 81 ++++++++++ 6 files changed, 409 insertions(+), 92 deletions(-) delete mode 100755 find-all-obnam-errors create mode 100644 manual/en/800-errors.mdwn create mode 100644 manual/en/801-errors.mdwn create mode 100644 obnamlib/plugins/list_errors_plugin.py create mode 100644 obnamlib/structurederror_finder.py diff --git a/find-all-obnam-errors b/find-all-obnam-errors deleted file mode 100755 index 602ae990..00000000 --- a/find-all-obnam-errors +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014-2015 Lars Wirzenius -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# =*= License: GPL-3+ =*= - - -import imp -import inspect -import os - -import cliapp - -import obnamlib -import obnamlib.plugins - - -def get_modules(module): - objs = [getattr(module, name) for name in dir(module)] - return [x for x in objs if inspect.ismodule(x)] - - -def get_modules_recursively(module): - result = [] - queue = get_modules(module) - while queue: - item = queue.pop() - if item not in result: - result.append(item) - queue.extend(get_modules(item)) - return result - - -def get_obnam_errors(module): - - def is_obnam_error(obj): - return (type(obj) is type and - issubclass(x, obnamlib.ObnamError) and - hasattr(x, 'msg')) - - objs = [getattr(module, name) for name in dir(module)] - return [x for x in objs if is_obnam_error(x)] - - -all_modules = get_modules_recursively(obnamlib) - -# This is a bit of magic to load all the modules that contain plugins. -def find_plugin_modules(*args): - for module_filename in app.pluginmgr.plugin_files: - module_name, ext = os.path.splitext(os.path.basename(module_filename)) - with open(module_filename) as f: - module = imp.load_module(module_name, f, module_filename, - ('.py', 'r', imp.PY_SOURCE)) - all_modules.append(module) - - -# This is a terrible kludge. -app = obnamlib.App() -app.process_args = find_plugin_modules -app.run() - -template = '''\ -{id} ({class_name}): {msg} -''' - -error_classes = set() -for module in set(all_modules): - for error_class in get_obnam_errors(module): - error_classes.add(error_class) - -def class_name(error_class): - return error_class.__name__ - -for error_class in sorted(error_classes, key=class_name): - e = error_class() - print template.format( - class_name=class_name(error_class), - id=e.id, - msg=e.msg, - ), diff --git a/manual/en/800-errors.mdwn b/manual/en/800-errors.mdwn new file mode 100644 index 00000000..e42580cf --- /dev/null +++ b/manual/en/800-errors.mdwn @@ -0,0 +1,10 @@ +Appendix: Error messages +======================== + +This appendix lists all Obnam error messages and their explanations. +It is possible you'll see other error messages while running Obnam. +These are not listed here, as Obnam doesn't know about them. + +The errors are listed twice: briefly, in order of their unique +error, and then more fully, in alphabetical order. + diff --git a/manual/en/801-errors.mdwn b/manual/en/801-errors.mdwn new file mode 100644 index 00000000..084f2798 --- /dev/null +++ b/manual/en/801-errors.mdwn @@ -0,0 +1,269 @@ +## By error code + +* `R018FCX ToplevelIsFileError` +* `R01F56X RepositorySettingMissingError` +* `R02C17X HardlinkError` +* `R0B15DX RepositoryGenerationDoesNotExist` +* `R0BE94X RepositoryClientNotLocked` +* `R0C79EX GpgError` +* `R0F22CX URLSchemeAlreadyRegisteredError` +* `R0FC21X SetMetadataError` +* `R169C6X MissingFilterError` +* `R173AEX NoFilterTagError` +* `R1A025X RepositoryClientKeyNotAllowed` +* `R1CA00X ClientDoesNotExistError` +* `R22E66X SizeSyntaxError` +* `R24424X RepositoryClientDoesNotExist` +* `R283A6X UnitNameError` +* `R2FA37X WrongNumberOfGenerationSettingsError` +* `R338F2X BackupRootMissingError` +* `R3B42AX WrongNumberOfGenerationsForVerify` +* `R3E151X RepositoryFileDoesNotExistInGeneration` +* `R3E1C1X RestoreTargetNotEmpty` +* `R41CE6X RepositoryClientAlreadyExists` +* `R43272X RepositoryChunkDoesNotExist` +* `R45B50X DuplicatePeriodError` +* `R47416X WrongHostKeyError` +* `R4C3BCX BackupErrors` +* `R57207X RepositoryClientGenerationUnfinished` +* `R5914DX InvalidPortError` +* `R5F98AX NoHostKeyError` +* `R681AEX LockFail` +* `R6A098X RepositoryGenerationKeyNotAllowed` +* `R6C1C8X RepositoryClientListNotLocked` +* `R6EAF2X RepositoryClientLockingFailed` +* `R7137EX BagIdNotSetError` +* `R79699X RepositoryFileKeyNotAllowed` +* `R79ED6X BackupRootDoesNotExist` +* `R7B8D0X FileNotFoundError` +* `R826A1X UnknownVFSError` +* `R8AAC1X NoHostKeyOfWantedTypeError` +* `R8F974X RepositoryChunkIndexesLockingFailed` +* `R91CA1X ShowFirstGenerationError` +* `R9808DX ForgetPolicySyntaxError` +* `RA4F35X RootIsNotADirectory` +* `RA5942X WrongNumberOfGenerationsForDiffError` +* `RA7D64X UnknownRepositoryFormatWanted` +* `RA881CX RepositoryChunkContentNotInIndexes` +* `RA920EX NotARepository` +* `RABC26X FuseModuleNotFoundError` +* `RB1048X RepositoryClientListLockingFailed` +* `RB4324X GAImmutableError` +* `RB8E98X WrongURLSchemeError` +* `RB927BX SeparatorError` +* `RBF6DDX RepositoryAccessError` +* `RCB0CAX KeyAuthenticationError` +* `RCE08AX ObnamIOError` +* `RCEF5CX MallocError` +* `RD5FA4X ObnamSystemError` +* `RD6259X RestoreErrors` +* `RDF30DX Fail` +* `RE187FX RepositoryChunkIndexesNotLocked` +* `REFB32X RepositoryClientHasNoGenerations` +* `RF4EFDX UnknownRepositoryFormat` + + +## By name + +`BackupErrors` (`R4C3BCX`) +: There were errors during the backup + +`BackupRootDoesNotExist` (`R79ED6X`) +: Backup root does not exist or is not a directory: {root} + +`BackupRootMissingError` (`R338F2X`) +: No backup roots specified + +`BagIdNotSetError` (`R7137EX`) +: Bag id not set: cannot append a blob (programming error) + +`ClientDoesNotExistError` (`R1CA00X`) +: Client {client} does not exist in repository {repo} + +`DuplicatePeriodError` (`R45B50X`) +: Forget policy may not duplicate period ({period}): {policy} + +`Fail` (`RDF30DX`) +: {filename}: {reason} + +`FileNotFoundError` (`R7B8D0X`) +: FUSE: File not found: {filename} + +`ForgetPolicySyntaxError` (`R9808DX`) +: Forget policy syntax error: {policy} + +`FuseModuleNotFoundError` (`RABC26X`) +: Failed to load module "fuse", try installing python-fuse + +`GAImmutableError` (`RB4324X`) +: Attempt to modify an immutable GADirectory + +`GpgError` (`R0C79EX`) +: gpg failed with exit code {returncode}: {stderr} + +`HardlinkError` (`R02C17X`) +: Cannot hardlink on SFTP; sorry + + This is due to a limitation in the Python paramiko library that + Obnam uses for SSH/SFTP access. + +`InvalidPortError` (`R5914DX`) +: Invalid port number {port} in {url}: {error} + +`KeyAuthenticationError` (`RCB0CAX`) +: Can't authenticate to SSH server using key + +`LockFail` (`R681AEX`) +: Couldn't create lock {lock_name}: {reason} + +`MallocError` (`RCEF5CX`) +: malloc out of memory while calling {function} + +`MissingFilterError` (`R169C6X`) +: Unknown filter tag: {tagname} + +`NoFilterTagError` (`R173AEX`) +: No filter tag found + +`NoHostKeyError` (`R5F98AX`) +: No known host key for {hostname} + +`NoHostKeyOfWantedTypeError` (`R8AAC1X`) +: No known type {key_type} host key for {hostname} + +`NotARepository` (`RA920EX`) +: {url} does not seem to be an Obnam repository + +`ObnamIOError` (`RCE08AX`) +: I/O error: {filename}: {errno}: {strerror} + +`ObnamSystemError` (`RD5FA4X`) +: System error: {filename}: {errno}: {strerror} + +`RepositoryAccessError` (`RBF6DDX`) +: Repository does not exist or cannot be accessed: {error} + +`RepositoryChunkContentNotInIndexes` (`RA881CX`) +: Repository chunk indexes do not contain content + +`RepositoryChunkDoesNotExist` (`R43272X`) +: Repository doesn't contain chunk {chunk_id}. It is expected at + {filename} + +`RepositoryChunkIndexesLockingFailed` (`R8F974X`) +: Repository chunk indexes are already locked + +`RepositoryChunkIndexesNotLocked` (`RE187FX`) +: Repository chunk indexes are not locked + +`RepositoryClientAlreadyExists` (`R41CE6X`) +: Repository client {client_name} already exists + +`RepositoryClientDoesNotExist` (`R24424X`) +: Repository client {client_name} does not exist + +`RepositoryClientGenerationUnfinished` (`R57207X`) +: Cannot start new generation for {client_name}: previous one is not + finished yet (programming error) + +`RepositoryClientHasNoGenerations` (`REFB32X`) +: Client {client_name} has no generations + +`RepositoryClientKeyNotAllowed` (`R1A025X`) +: Client {client_name} uses repository format {format} which does + not allow the key {key_name} to be use for clients + +`RepositoryClientListLockingFailed` (`RB1048X`) +: Repository client list could not be locked + +`RepositoryClientListNotLocked` (`R6C1C8X`) +: Repository client list is not locked + +`RepositoryClientLockingFailed` (`R6EAF2X`) +: Repository client {client_name} could not be locked + +`RepositoryClientNotLocked` (`R0BE94X`) +: Repository client {client_name} is not locked + +`RepositoryFileDoesNotExistInGeneration` (`R3E151X`) +: Client {client_name}, generation {genspec} does not have file + {filename} + +`RepositoryFileKeyNotAllowed` (`R79699X`) +: Client {client_name} uses repository format {format} which does + not allow the key {key_name} to be use for files + +`RepositoryGenerationDoesNotExist` (`R0B15DX`) +: Cannot find requested generation {gen_id!r} for client + {client_name} + +`RepositoryGenerationKeyNotAllowed` (`R6A098X`) +: Client {client_name} uses repository format {format} which does + not allow the key {key_name} to be used for generations + +`RepositorySettingMissingError` (`R01F56X`) +: No --repository setting. You need to specify it on the command + line or a configuration file + +`RestoreErrors` (`RD6259X`) +: There were errors when restoring + + See previous error messages for details. + +`RestoreTargetNotEmpty` (`R3E1C1X`) +: The restore --to directory ({to}) is not empty. + +`RootIsNotADirectory` (`RA4F35X`) +: {baseurl} is not a directory, but a VFS root must be a directory + +`SeparatorError` (`RB927BX`) +: Forget policy must have rules separated by commas, see position + {position}: {policy} + +`SetMetadataError` (`R0FC21X`) +: {filename}: Couldn't set metadata {metadata}: {errno}: {strerror} + +`ShowFirstGenerationError` (`R91CA1X`) +: Can't show first generation. Use 'obnam ls' instead + +`SizeSyntaxError` (`R22E66X`) +: "{size}" is not a valid size + +`ToplevelIsFileError` (`R018FCX`) +: File at repository root: {filename} + +`URLSchemeAlreadyRegisteredError` (`R0F22CX`) +: VFS URL scheme {scheme} already registered + +`UnitNameError` (`R283A6X`) +: "{unit}" is not a valid unit + +`UnknownRepositoryFormat` (`RF4EFDX`) +: Unknown format {format} at {url} + +`UnknownRepositoryFormatWanted` (`RA7D64X`) +: Unknown format {format} requested + +`UnknownVFSError` (`R826A1X`) +: Unknown VFS type: {url} + +`WrongHostKeyError` (`R47416X`) +: SSH server {hostname} offered wrong public key + + Note that this may due to an obsolete host key in your "known + hosts" file. If so, use "ssh-key -R" to remove it. However, it can + also be a sign that someone is trying to hijack your connection to + your server, and you should be careful. + +`WrongNumberOfGenerationSettingsError` (`R2FA37X`) +: The restore command wants exactly one generation option + +`WrongNumberOfGenerationsForDiffError` (`RA5942X`) +: Need one or two generations + +`WrongNumberOfGenerationsForVerify` (`R3B42AX`) +: verify must be given exactly one generation + +`WrongURLSchemeError` (`RB8E98X`) +: SftpFS used with non-sftp URL: {url} + diff --git a/obnamlib/__init__.py b/obnamlib/__init__.py index cdb98e9e..ab4fb440 100644 --- a/obnamlib/__init__.py +++ b/obnamlib/__init__.py @@ -19,6 +19,7 @@ import cliapp from .version import __version__ from .structurederror import StructuredError +from .structurederror_finder import find_structured_errors from .obnamerror import ObnamError from .defaults import ( DEFAULT_NODE_SIZE, diff --git a/obnamlib/plugins/list_errors_plugin.py b/obnamlib/plugins/list_errors_plugin.py new file mode 100644 index 00000000..2b3d0e67 --- /dev/null +++ b/obnamlib/plugins/list_errors_plugin.py @@ -0,0 +1,48 @@ +# Copyright (C) 2015 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import textwrap + +import obnamlib + + +class ListErrorsPlugin(obnamlib.ObnamPlugin): + + def enable(self): + self.app.add_subcommand( + 'list-errors', self.list_errors, arg_synopsis='') + + def list_errors(self, args): + errors = obnamlib.find_structured_errors(obnamlib, self.app.pluginmgr) + f = self.app.output + + f.write('## By error code\n\n') + for error in sorted(errors, key=lambda e: e().id): + f.write('* `{} {}`\n'.format(error().id, error.__name__)) + f.write('\n\n') + + f.write('## By name\n\n') + for error in sorted(errors, key=lambda e: e.__name__): + f.write('`{}` (`{}`)\n'.format(error.__name__, error().id)) + f.write(': {}\n'.format(self.indent(error.msg).lstrip())) + f.write('\n') + + def indent(self, s): + s = ''.join(s.rstrip() + '\n' for s in s.splitlines()) + paras = ['\n'.join(para.split()) for para in s.split('\n\n') if para.strip()] + return '\n\n'.join( + textwrap.fill(para, initial_indent=' '*4, subsequent_indent=' '*4) + for para in paras) diff --git a/obnamlib/structurederror_finder.py b/obnamlib/structurederror_finder.py new file mode 100644 index 00000000..1de04c65 --- /dev/null +++ b/obnamlib/structurederror_finder.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# Copyright 2014-2015 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# =*= License: GPL-3+ =*= + + +# This module contains a fair bit of Python magic. Tread carefully. +# Like all the worst magic, it has no automated tests, either. Treat +# this module as if it were a sleeping dragon. Do not meddle in the +# affairs of dragons, for they are cranky and you are good with +# ketchup. + + +import imp +import inspect +import os + + +import obnamlib + + +def find_structured_errors(top_module, plugin_manager): + modules = _find_modules(top_module) + modules += _find_plugin_modules(plugin_manager) + return _find_errors(modules) + + +def _find_modules(top_module): + result = set() + queue = [top_module] + while queue: + module = queue.pop() + if module not in result: + result.add(module) + queue.extend(_get_submodules(module)) + return list(result) + + +def _get_submodules(module): + objs = [getattr(module, name) for name in dir(module)] + return [x for x in objs if inspect.ismodule(x)] + + +def _find_plugin_modules(plugin_manager): + modules = [] + for filename in plugin_manager.plugin_files: + module_name, ext = os.path.splitext(os.path.basename(filename)) + with open(filename, 'rb') as f: + module = imp.load_module( + module_name, f, filename, ('.py', 'r', imp.PY_SOURCE)) + modules.append(module) + return modules + + +def _find_errors(modules): + result = set() + for module in modules: + for name in dir(module): + obj = getattr(module, name) + if _is_structured_error(obj): + result.add(obj) + return list(result) + + +def _is_structured_error(obj): + return (type(obj) is type and + issubclass(obj, obnamlib.StructuredError) and + hasattr(obj, 'msg')) -- cgit v1.2.1