#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Univention App Center
# Exception classes
#
# Copyright 2015-2022 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <https://www.gnu.org/licenses/>.
#
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(AbortWithDetails, self).__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'
[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 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!')