From 1350ce8ad6e4d329e1228324dc85a3e993b6fad8 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Fri, 15 Mar 2024 10:08:35 -0700 Subject: [PATCH] Use NodesetNotFoundError class This error exception class went unused, likely due to complications from circular imports. To resolve this, move all of the configuration error exceptions into the exceptions.py file so they can be imported in both model.py and configloader.py. Change-Id: I19b0f078f4d215a2e14c2c7ed893ab225d1e1084 --- tests/unit/test_v3.py | 4 +- zuul/configloader.py | 220 +++------------------------- zuul/driver/gerrit/gerrittrigger.py | 2 +- zuul/driver/github/githubtrigger.py | 2 +- zuul/exceptions.py | 208 ++++++++++++++++++++++++++ zuul/model.py | 17 +-- zuul/scheduler.py | 2 +- 7 files changed, 239 insertions(+), 216 deletions(-) diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py index 02cea64faa..ebe96a8cf8 100644 --- a/tests/unit/test_v3.py +++ b/tests/unit/test_v3.py @@ -5199,7 +5199,7 @@ class TestValidateAllBroken(ZuulTestCase): scheduler_count = 1 def setUp(self): - self.assertRaises(zuul.configloader.ConfigurationSyntaxError, + self.assertRaises(zuul.exceptions.ConfigurationSyntaxError, super().setUp) def test_validate_all_tenants_broken(self): @@ -5223,7 +5223,7 @@ class TestValidateBroken(ZuulTestCase): scheduler_count = 1 def setUp(self): - self.assertRaises(zuul.configloader.ConfigurationSyntaxError, + self.assertRaises(zuul.exceptions.ConfigurationSyntaxError, super().setUp) def test_validate_tenant_broken(self): diff --git a/zuul/configloader.py b/zuul/configloader.py index 5e2d56101c..b7c9bab6f5 100644 --- a/zuul/configloader.py +++ b/zuul/configloader.py @@ -43,6 +43,23 @@ from zuul.lib.re2util import filter_allowed_disallowed, ZuulRegex from zuul.lib.varnames import check_varnames from zuul.zk.components import COMPONENT_REGISTRY from zuul.zk.semaphore import SemaphoreHandler +from zuul.exceptions import ( + SEVERITY_ERROR, + DuplicateGroupError, + DuplicateNodeError, + GlobalSemaphoreNotFoundError, + LabelForbiddenError, + MaxTimeoutError, + MultipleProjectConfigurations, + NodeFromGroupNotFoundError, + PipelineNotPermittedError, + ProjectNotFoundError, + ProjectNotPermittedError, + RegexDeprecation, + TemplateNotFoundError, + UnknownConnection, + YAMLDuplicateKeyError, +) ZUUL_CONF_ROOT = ('zuul.yaml', 'zuul.d', '.zuul.yaml', '.zuul.d') @@ -104,207 +121,6 @@ def indent(s): return '\n'.join([' ' + x for x in s.split('\n')]) -class ConfigurationSyntaxError(Exception): - zuul_error_name = 'Unknown Configuration Error' - zuul_error_severity = model.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): - message = textwrap.dedent("""\ - 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.format(project=project)) - 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 = model.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) - - class LocalAccumulator: """An error accumulator that wraps another accumulator (like LoadingErrors) while holding local context information. @@ -430,7 +246,7 @@ class LocalAccumulator: error_message = '\n\n'.join(msg) error_severity = getattr(error, 'zuul_error_severity', - model.SEVERITY_ERROR) + SEVERITY_ERROR) error_name = getattr(error, 'zuul_error_name', 'Unknown') config_error = model.ConfigurationError( diff --git a/zuul/driver/gerrit/gerrittrigger.py b/zuul/driver/gerrit/gerrittrigger.py index 81f280542d..8e83a72b61 100644 --- a/zuul/driver/gerrit/gerrittrigger.py +++ b/zuul/driver/gerrit/gerrittrigger.py @@ -24,7 +24,7 @@ from zuul.driver.util import ( make_regex, ZUUL_REGEX, ) -from zuul.configloader import DeprecationWarning +from zuul.exceptions import DeprecationWarning class GerritRequireApprovalDeprecation(DeprecationWarning): diff --git a/zuul/driver/github/githubtrigger.py b/zuul/driver/github/githubtrigger.py index e115691d3c..45ef00474e 100644 --- a/zuul/driver/github/githubtrigger.py +++ b/zuul/driver/github/githubtrigger.py @@ -19,7 +19,7 @@ from zuul.trigger import BaseTrigger from zuul.driver.github.githubmodel import GithubEventFilter from zuul.driver.github import githubsource from zuul.driver.util import scalar_or_list, to_list, make_regex, ZUUL_REGEX -from zuul.configloader import DeprecationWarning +from zuul.exceptions import DeprecationWarning class GithubUnlabelDeprecation(DeprecationWarning): diff --git a/zuul/exceptions.py b/zuul/exceptions.py index a332ba1afa..2ab47ba717 100644 --- a/zuul/exceptions.py +++ b/zuul/exceptions.py @@ -1,4 +1,5 @@ # 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 @@ -12,6 +13,12 @@ # 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): @@ -120,3 +127,204 @@ class IncorrectZuulAdminClaimError(AuthTokenUnauthorizedException): 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): + message = textwrap.dedent("""\ + 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.format(project=project)) + 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) diff --git a/zuul/model.py b/zuul/model.py index 11e61edff9..b6cb6f5e8d 100644 --- a/zuul/model.py +++ b/zuul/model.py @@ -52,6 +52,11 @@ from zuul.lib import tracing from zuul.zk import zkobject from zuul.zk.blob_store import BlobStore from zuul.zk.change_cache import ChangeKey +from zuul.exceptions import ( + SEVERITY_ERROR, + SEVERITY_WARNING, + NodesetNotFoundError, +) MERGER_MERGE = 1 # "git merge" MERGER_MERGE_RESOLVE = 2 # "git merge -s resolve" @@ -118,10 +123,6 @@ SCHEME_GOLANG = 'golang' SCHEME_FLAT = 'flat' SCHEME_UNIQUE = 'unique' -# Error severity -SEVERITY_ERROR = 'error' -SEVERITY_WARNING = 'warning' - def add_debug_line(debug_messages, msg, indent=0): if debug_messages is None: @@ -1620,7 +1621,7 @@ class NodeSet(ConfigObject): # This references an existing named nodeset in the layout. ns = layout.nodesets.get(nodeset) if ns is None: - raise Exception(f'The nodeset "{nodeset}" was not found.') + raise NodesetNotFoundError(nodeset) else: ns = nodeset if ns in history: @@ -2902,7 +2903,7 @@ class Job(ConfigObject): # This references an existing named nodeset in the layout. ns = layout.nodesets.get(nodeset) if ns is None: - raise Exception(f'The nodeset "{nodeset}" was not found.') + raise NodesetNotFoundError(nodeset) else: ns = nodeset return ns.flattenAlternatives(layout) @@ -3044,9 +3045,7 @@ class Job(ConfigObject): # This references an existing named nodeset in the layout. ns = layout.nodesets.get(self.nodeset) if ns is None: - raise Exception( - 'The nodeset "{nodeset}" was not found.'.format( - nodeset=self.nodeset)) + raise NodesetNotFoundError(self.nodeset) return ns return self.nodeset diff --git a/zuul/scheduler.py b/zuul/scheduler.py index 5d8ed27a68..8546564946 100644 --- a/zuul/scheduler.py +++ b/zuul/scheduler.py @@ -1438,7 +1438,7 @@ class Scheduler(threading.Thread): loading_errors.append(repr(error)) if loading_errors: summary = '\n\n\n'.join(loading_errors) - raise configloader.ConfigurationSyntaxError( + raise exceptions.ConfigurationSyntaxError( f"Configuration errors: {summary}") duration = round(time.monotonic() - start, 3)