Source code for univention.appcenter.exceptions
#!/usr/bin/python3
#
# Univention App Center
# Exception classes
#
# SPDX-FileCopyrightText: 2015-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
#
import re
from univention.appcenter.utils import _, unique
[docs]
class Abort(Exception):
'''
The Abort class is the base class for a "controlled" abortion of an
action (meaning: univention-app <action>). This means that this
situation was foreseen by the developers and is less critical. The
"code" variable is sent to the App Center server for Univention to get
a clue what went wrong.
You should only use one class in different
places in the code if you are confident that sending this error from
two places does not lead to irritation. (This explicitly holds for
Abort itself, you better subclass it)
If you give a "default_error_msg" variable, you may also use %(var)s
for formatting in this string. In this case, the __init__ method will
require the class to be initiated with "var" (either positional or
non-positional). The last argument (or an argument named "message") can
overwrite any "default_error_msg".
'''
code = 401
default_error_msg = ''
def __init__(self, *args, **kwargs):
keys = re.findall(r'%\(([^)]+)\)', self.default_error_msg)
keys = unique(keys)
i = 0
_args = []
for key in keys:
if key in kwargs:
value = kwargs[key]
else:
try:
value = args[i]
except IndexError:
raise TypeError('Need %s for %s' % (key, self.__class__.__name__))
i += 1
setattr(self, key, value)
_args.append(value)
if len(args) > i:
self.message = args[i]
elif 'message' in kwargs:
self.message = kwargs.get('message')
else:
self.message = ''
self.args = tuple(_args)
[docs]
def get_exc_details(self):
return None
def __str__(self):
if self.message:
return self.message
else:
return self.default_error_msg % self.__dict__
[docs]
class AbortWithDetails(Abort):
def __init__(self, *args, **kwargs):
self._exc_details = kwargs
super().__init__(*args, **kwargs)
[docs]
def get_exc_details(self):
if self._exc_details:
return self._exc_details
[docs]
class NetworkError(Abort):
code = 402
[docs]
class CredentialsNoUsernameError(Abort):
code = 403
[docs]
class CredentialsNoPasswordError(Abort):
code = 404
[docs]
class ConnectionFailed(Abort):
code = 405
default_error_msg = 'No connection possible'
[docs]
class ConnectionFailedSecretFile(ConnectionFailed):
code = 406
default_error_msg = '/etc/machine.secret not readable'
[docs]
class ConnectionFailedInvalidAdminCredentials(ConnectionFailed):
code = 407
default_error_msg = 'LDAP server does not accept admin password!'
[docs]
class ConnectionFailedInvalidMachineCredentials(ConnectionFailed):
code = 408
default_error_msg = 'LDAP server does not accept machine password!'
[docs]
class ConnectionFailedInvalidUserCredentials(ConnectionFailed):
code = 409
default_error_msg = 'Too many failed attempts!'
[docs]
class ConnectionFailedServerDown(ConnectionFailed):
code = 410
default_error_msg = 'LDAP server is not running!'
[docs]
class UpdateSignatureVerificationFailed(Abort):
code = 411
default_error_msg = 'Signature verification for %(filename)s failed. GPG Error: %(gpg_error)s. File to verify and signature have a mtime offset of: %(time_diff)s.'
[docs]
class UpdateUnpackArchiveFailed(Abort):
code = 412
default_error_msg = 'Failed to unpack "%(filename)s"'
[docs]
class ShellNoCommandError(Abort):
code = 414
default_error_msg = 'Cannot run command: No command specified'
[docs]
class ShellAppNotRunning(Abort):
code = 415
default_error_msg = 'Cannot run command: %(app)s is not running in a container'
[docs]
class ShellContainerNotFound(Abort):
code = 440
default_error_msg = 'Cannot run command: No container found for %(service)s of %(app)s'
[docs]
class InstallSetupFailed(AbortWithDetails):
code = 416
default_error_msg = 'Setup script failed!'
# TODO: AbortWithDetails
[docs]
class DockerCouldNotStartContainer(Abort):
code = 417
default_error_msg = 'Unable to start the container!'
[docs]
def get_exc_details(self):
return str(self)
[docs]
class DatabaseConnectorError(Abort):
code = 418
[docs]
def get_exc_details(self):
return str(self)
[docs]
class InstallNonDockerVersionError(Abort):
code = 419
default_error_msg = 'Cannot use %(app)s as docker is to be ignored, yet, only non-docker versions could be found'
# TODO: AbortWithDetails
[docs]
class InstallFailed(Abort):
code = 420
default_error_msg = 'Failed to install the App'
[docs]
class InstallMasterPackagesNoninteractiveError(Abort):
code = 421
[docs]
class InstallMasterPackagesPasswordError(Abort):
code = 422
[docs]
class RemoveBackupFailed(Abort):
code = 423
default_error_msg = 'Could not backup container!'
[docs]
class RemovePluginUnsupported(Abort):
code = 424
default_error_msg = 'Uninstallation of a plugin is not supported!'
[docs]
class RegisterSchemaFailed(AbortWithDetails):
code = 425
default_error_msg = 'Registration of schema extension failed (Code: %(code)s)'
[docs]
def get_exc_details(self):
return str(self)
[docs]
class RegisterSchemaFileFailed(Abort):
code = 426
default_error_msg = 'Registering schema file %(filename)s failed'
[docs]
def get_exc_details(self):
return str(self)
# Not used. Here for reference (and to prevent re-using the code)
[docs]
class DockerVerificationFailed(Abort):
code = 427
[docs]
def get_exc_details(self):
return str(self)
[docs]
class LocalAppCenterError(Abort):
# this is a bit lazy...
code = 428
[docs]
class UpgradeStartContainerFailed(Abort):
code = 429
default_error_msg = 'Could not start the app container. It needs to be running to be upgraded!'
[docs]
class UpgradeBackupFailed(Abort):
code = 430
default_error_msg = 'Could not backup container!'
[docs]
class UpgradeAppFailed(Abort):
code = 431
default_error_msg = 'App upgrade script failed'
[docs]
class UpgradePackagesFailed(Abort):
code = 432
default_error_msg = 'Package upgrade script failed'
[docs]
class UpgradeReleaseFailed(Abort):
code = 433
default_error_msg = 'Release upgrade script failed'
[docs]
class ConnectionFailedConnectError(ConnectionFailed):
code = 434
def __init__(self, exc):
self.details = exc.args[0]
def __str__(self):
msg = _('LDAP connection refused. There may be an issue with the certificate of the LDAP server. Please also check the proxy and firewall settings, if any.')
details = None
try:
details = self.details.get('info', 'No further details')
except (IndexError, KeyError):
pass
if details:
msg += ' (' + details + ')'
return msg
[docs]
class DockerImagePullFailed(AbortWithDetails):
code = 435
default_error_msg = 'Downloading Docker image %(image)s failed: %(out)s'
# TODO: AbortWithDetails
[docs]
class RemoveFailed(Abort):
code = 436
default_error_msg = 'Failed to uninstall the App'
[docs]
class ParallelOperationInProgress(Abort):
code = 437
default_error_msg = 'Another package operation is in progress'
[docs]
class InstallWithoutPermissionError(Abort):
code = 438
default_error_msg = 'The App requires install permissions which are missing. Please contact the App Provider.'
[docs]
class ReinitializeError(Abort):
code = 439
default_error_msg = 'Reinitializing the App failed.'
[docs]
class AppCenterError(Exception):
'''
A "real" exception that developers cannot handle very well.
The difference between AppCenterError and Abort is that Abort is a
somewhat defined behavior, i.e. App installation has to fail if the
setup script fails. AppCenterError happens where it was not supposed
to.
The difference between AppCenterError and Exception is that
AppCenterError gives a nicer feedback for the Administrator than a
scary traceback. You can even put custom information into the proposed
feedback mail (raise AppCenterError(str(custom))).
As with Abort, AppCenterError should be subclassed and get a different
code.
'''
code = 500
title = _('An error occurred!')
info = _('We are sorry for the inconvenience. Please help us to improve the App Center and the Apps by sending us the information below.')
[docs]
class AppCenterErrorContainerStart(AppCenterError):
code = 501
title = _('The docker container could not be started!')
[docs]
class ResidualInstallationError(AppCenterError):
code = 502
title = _('An unexpected error occured before the installation started!')