zuul/zuul/exceptions.py

344 lines
11 KiB
Python

# Copyright 2015 Rackspace Australia
# Copyright 2023 Acme Gating, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import textwrap
# Error severity
SEVERITY_ERROR = 'error'
SEVERITY_WARNING = 'warning'
class ChangeNotFound(Exception):
def __init__(self, number, ps):
self.number = number
self.ps = ps
self.change = "%s,%s" % (str(number), str(ps))
message = "Change %s not found" % self.change
super(ChangeNotFound, self).__init__(message)
class RevNotFound(Exception):
def __init__(self, project, rev):
self.project = project
self.revision = rev
message = ("Failed to checkout project '%s' at revision '%s'"
% (self.project, self.revision))
super(RevNotFound, self).__init__(message)
class MergeFailure(Exception):
pass
class ConfigurationError(Exception):
pass
class StreamingError(Exception):
pass
# Authentication Exceptions
class AuthTokenException(Exception):
defaultMsg = 'Unknown Error'
HTTPError = 400
def __init__(self, realm=None, msg=None):
super(AuthTokenException, self).__init__(msg or self.defaultMsg)
self.realm = realm
self.error = self.__class__.__name__
self.error_description = msg or self.defaultMsg
def getAdditionalHeaders(self):
return {}
class JWKSException(AuthTokenException):
defaultMsg = 'Unknown error involving JSON Web Key Set'
class AuthTokenForbiddenException(AuthTokenException):
defaultMsg = 'Insufficient privileges'
HTTPError = 403
class AuthTokenUnauthorizedException(AuthTokenException):
defaultMsg = 'This action requires authentication'
HTTPError = 401
def getAdditionalHeaders(self):
error_header = '''Bearer realm="%s"
error="%s"
error_description="%s"'''
return {"WWW-Authenticate": error_header % (self.realm,
self.error,
self.error_description)}
class AuthTokenUndecodedException(AuthTokenUnauthorizedException):
defaultMsg = 'Auth Token could not be decoded'
class AuthTokenInvalidSignatureException(AuthTokenUnauthorizedException):
defaultMsg = 'Invalid signature'
class BearerTokenRequiredError(AuthTokenUnauthorizedException):
defaultMsg = 'Authorization with bearer token required'
class IssuerUnknownError(AuthTokenUnauthorizedException):
defaultMsg = 'Issuer unknown'
class MissingClaimError(AuthTokenUnauthorizedException):
defaultMsg = 'Token is missing claims'
class IncorrectAudienceError(AuthTokenUnauthorizedException):
defaultMsg = 'Incorrect audience'
class TokenExpiredError(AuthTokenUnauthorizedException):
defaultMsg = 'Token has expired'
class MissingUIDClaimError(MissingClaimError):
defaultMsg = 'Token is missing id claim'
class IncorrectZuulAdminClaimError(AuthTokenUnauthorizedException):
defaultMsg = (
'The "zuul.admin" claim is expected to be a list of tenants')
class UnauthorizedZuulAdminClaimError(AuthTokenUnauthorizedException):
defaultMsg = 'Issuer is not allowed to set "zuul.admin" claim'
class ConfigurationSyntaxError(Exception):
zuul_error_name = 'Unknown Configuration Error'
zuul_error_severity = SEVERITY_ERROR
class NodeFromGroupNotFoundError(ConfigurationSyntaxError):
zuul_error_name = 'Node From Group Not Found'
def __init__(self, nodeset, node, group):
message = textwrap.dedent("""\
In {nodeset} the group "{group}" contains a
node named "{node}" which is not defined in the nodeset.""")
message = textwrap.fill(message.format(nodeset=nodeset,
node=node, group=group))
super(NodeFromGroupNotFoundError, self).__init__(message)
class DuplicateNodeError(ConfigurationSyntaxError):
zuul_error_name = 'Duplicate Node'
def __init__(self, nodeset, node):
message = textwrap.dedent("""\
In nodeset "{nodeset}" the node "{node}" appears multiple times.
Node names must be unique within a nodeset.""")
message = textwrap.fill(message.format(nodeset=nodeset,
node=node))
super(DuplicateNodeError, self).__init__(message)
class UnknownConnection(ConfigurationSyntaxError):
zuul_error_name = 'Unknown Connection'
def __init__(self, connection_name):
message = textwrap.dedent("""\
Unknown connection named "{connection}".""")
message = textwrap.fill(message.format(connection=connection_name))
super(UnknownConnection, self).__init__(message)
class LabelForbiddenError(ConfigurationSyntaxError):
zuul_error_name = 'Label Forbidden'
def __init__(self, label, allowed_labels, disallowed_labels):
message = textwrap.dedent("""\
Label named "{label}" is not part of the allowed
labels ({allowed_labels}) for this tenant.""")
# Make a string that looks like "a, b and not c, d" if we have
# both allowed and disallowed labels.
labels = ", ".join(allowed_labels or [])
if allowed_labels and disallowed_labels:
labels += ' and '
if disallowed_labels:
labels += 'not '
labels += ", ".join(disallowed_labels)
message = textwrap.fill(message.format(
label=label,
allowed_labels=labels))
super(LabelForbiddenError, self).__init__(message)
class MaxTimeoutError(ConfigurationSyntaxError):
zuul_error_name = 'Max Timeout Exceeded'
def __init__(self, job, tenant):
message = textwrap.dedent("""\
The job "{job}" exceeds tenant max-job-timeout {maxtimeout}.""")
message = textwrap.fill(message.format(
job=job.name, maxtimeout=tenant.max_job_timeout))
super(MaxTimeoutError, self).__init__(message)
class DuplicateGroupError(ConfigurationSyntaxError):
zuul_error_name = 'Duplicate Nodeset Group'
def __init__(self, nodeset, group):
message = textwrap.dedent("""\
In {nodeset} the group "{group}" appears multiple times.
Group names must be unique within a nodeset.""")
message = textwrap.fill(message.format(nodeset=nodeset,
group=group))
super(DuplicateGroupError, self).__init__(message)
class ProjectNotFoundError(ConfigurationSyntaxError):
zuul_error_name = 'Project Not Found'
def __init__(self, project):
projects = None
if isinstance(project, (list, tuple)):
if len(project) > 1:
projects = ', '.join(f'"{p}"' for p in project)
else:
project = project[0]
if projects:
message = textwrap.dedent(f"""\
The projects {projects} were not found. All projects
referenced within a Zuul configuration must first be
added to the main configuration file by the Zuul
administrator.""")
else:
message = textwrap.dedent(f"""\
The project "{project}" was not found. All projects
referenced within a Zuul configuration must first be
added to the main configuration file by the Zuul
administrator.""")
message = textwrap.fill(message)
super(ProjectNotFoundError, self).__init__(message)
class TemplateNotFoundError(ConfigurationSyntaxError):
zuul_error_name = 'Template Not Found'
def __init__(self, template):
message = textwrap.dedent("""\
The project template "{template}" was not found.
""")
message = textwrap.fill(message.format(template=template))
super(TemplateNotFoundError, self).__init__(message)
class NodesetNotFoundError(ConfigurationSyntaxError):
zuul_error_name = 'Nodeset Not Found'
def __init__(self, nodeset):
message = textwrap.dedent("""\
The nodeset "{nodeset}" was not found.
""")
message = textwrap.fill(message.format(nodeset=nodeset))
super(NodesetNotFoundError, self).__init__(message)
class PipelineNotPermittedError(ConfigurationSyntaxError):
zuul_error_name = 'Pipeline Forbidden'
def __init__(self):
message = textwrap.dedent("""\
Pipelines may not be defined in untrusted repos,
they may only be defined in config repos.""")
message = textwrap.fill(message)
super(PipelineNotPermittedError, self).__init__(message)
class ProjectNotPermittedError(ConfigurationSyntaxError):
zuul_error_name = 'Project Forbidden'
def __init__(self):
message = textwrap.dedent("""\
Within an untrusted project, the only project definition
permitted is that of the project itself.""")
message = textwrap.fill(message)
super(ProjectNotPermittedError, self).__init__(message)
class GlobalSemaphoreNotFoundError(ConfigurationSyntaxError):
zuul_error_name = 'Global Semaphore Not Found'
def __init__(self, semaphore):
message = textwrap.dedent("""\
The global semaphore "{semaphore}" was not found. All
global semaphores must be added to the main configuration
file by the Zuul administrator.""")
message = textwrap.fill(message.format(semaphore=semaphore))
super(GlobalSemaphoreNotFoundError, self).__init__(message)
class YAMLDuplicateKeyError(ConfigurationSyntaxError):
def __init__(self, key, source_context, start_mark):
self.source_context = source_context
self.start_mark = start_mark
message = (f'The key "{key}" appears more than once; '
'duplicate keys are not permitted.')
super(YAMLDuplicateKeyError, self).__init__(message)
class ConfigurationSyntaxWarning:
zuul_error_name = 'Unknown Configuration Warning'
zuul_error_severity = SEVERITY_WARNING
zuul_error_message = 'Unknown Configuration Warning'
def __init__(self, message=None):
if message:
self.zuul_error_message = message
class MultipleProjectConfigurations(ConfigurationSyntaxWarning):
zuul_error_name = 'Multiple Project Configurations'
zuul_error_problem = 'configuration error'
def __init__(self, source_context):
message = textwrap.dedent(f"""\
Configuration in {source_context.path} ignored because project-branch
is already configured.""")
message = textwrap.fill(message)
super().__init__(message)
class DeprecationWarning(ConfigurationSyntaxWarning):
zuul_error_problem = 'deprecated syntax'
class RegexDeprecation(DeprecationWarning):
zuul_error_name = 'Regex Deprecation'
zuul_error_message = """\
All regular expressions must conform to RE2 syntax, but an
expression using the deprecated Perl-style syntax has been detected.
Adjust the configuration to conform to RE2 syntax."""
def __init__(self, message=None):
if message:
message = (self.zuul_error_message +
f"\n\nThe RE2 syntax error is: {message}")
super().__init__(message)