# 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 class DependencyLimitExceededError(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)