Add Gerrit pipeline trigger requirements
This updates the Gerrit driver to match the pattern in the GitHub driver where instead of specifying individual trigger requirements such as "require-approvals", instead a complete ref filter (a la "requirements") can be embedded in the trigger filter. The "require-approvals" and "reject-approvals" attributes are deprecated in favor of the new approach. Additionally, all require filters in Gerrit are now available as reject filters. And finally, the Gerrit filters are updated to return FalseWithReason so that log messages are more useful, and the Github filters are updated to improve the language, avoid apostraphes for ease of grepping, and match the new Gerrit filters. Change-Id: Ia9c749f1c8e318fe01e84e52831a9d0d2c10b203
This commit is contained in:
parent
1a4ec7e926
commit
546ad5353a
|
@ -239,6 +239,10 @@ be able to invoke the ``gerrit stream-events`` command over SSH.
|
|||
|
||||
.. attr:: require-approval
|
||||
|
||||
.. warning:: This is deprecated and will be removed in a future
|
||||
version. Use :attr:`pipeline.trigger.<gerrit
|
||||
source>.require` instead.
|
||||
|
||||
This may be used for any event. It requires that a certain kind
|
||||
of approval be present for the current patchset of the change
|
||||
(the approval could be added by the event in question). It
|
||||
|
@ -246,13 +250,40 @@ be able to invoke the ``gerrit stream-events`` command over SSH.
|
|||
source>.approval`. For each specified criteria there must exist
|
||||
a matching approval.
|
||||
|
||||
This is ignored if the :attr:`pipeline.trigger.<gerrit
|
||||
source>.require` attribute is present.
|
||||
|
||||
.. attr:: reject-approval
|
||||
|
||||
.. warning:: This is deprecated and will be removed in a future
|
||||
version. Use :attr:`pipeline.trigger.<gerrit
|
||||
source>.reject` instead.
|
||||
|
||||
This takes a list of approvals in the same format as
|
||||
:attr:`pipeline.trigger.<gerrit source>.require-approval` but
|
||||
the item will fail to enter the pipeline if there is a matching
|
||||
approval.
|
||||
|
||||
This is ignored if the :attr:`pipeline.trigger.<gerrit
|
||||
source>.reject` attribute is present.
|
||||
|
||||
.. attr:: require
|
||||
|
||||
This may be used for any event. It describes conditions that
|
||||
must be met by the change in order for the trigger event to
|
||||
match. Those conditions may be satisfied by the event in
|
||||
question. It follows the same syntax as
|
||||
:ref:`gerrit_requirements`.
|
||||
|
||||
.. attr:: reject
|
||||
|
||||
This may be used for any event and is the mirror of
|
||||
:attr:`pipeline.trigger.<gerrit source>.require`. It describes
|
||||
conditions that when met by the change cause the trigger event
|
||||
not to match. Those conditions may be satisfied by the event in
|
||||
question. It follows the same syntax as
|
||||
:ref:`gerrit_requirements`.
|
||||
|
||||
Reporter Configuration
|
||||
----------------------
|
||||
|
||||
|
@ -284,6 +315,8 @@ with an HTTP password, in which case the HTTP API is used.
|
|||
A :ref:`connection<connections>` that uses the gerrit driver must be
|
||||
supplied to the trigger.
|
||||
|
||||
.. _gerrit_requirements:
|
||||
|
||||
Requirements Configuration
|
||||
--------------------------
|
||||
|
||||
|
@ -366,7 +399,7 @@ order to be enqueued into the pipeline.
|
|||
.. attr:: status
|
||||
|
||||
A string value that corresponds with the status of the change
|
||||
reported by the trigger.
|
||||
reported by Gerrit.
|
||||
|
||||
.. attr:: pipeline.reject.<gerrit source>
|
||||
|
||||
|
@ -376,10 +409,12 @@ order to be enqueued into the pipeline.
|
|||
|
||||
.. attr:: approval
|
||||
|
||||
This takes an approval or a list of approvals. If an approval
|
||||
matches the provided criteria the change can not be entered
|
||||
into the pipeline. It follows the same syntax as
|
||||
:attr:`pipeline.require.<gerrit source>.approval`.
|
||||
This requires that a certain kind of approval not be present for the
|
||||
current patchset of the change (the approval could be added by
|
||||
the event in question). Approval is a dictionary or a list of
|
||||
dictionaries with attributes listed below, all of which are
|
||||
optional and are combined together so that there must be no approvals
|
||||
matching all specified requirements.
|
||||
|
||||
Example to reject a change with any negative vote:
|
||||
|
||||
|
@ -390,6 +425,55 @@ order to be enqueued into the pipeline.
|
|||
approval:
|
||||
- Code-Review: [-1, -2]
|
||||
|
||||
.. attr:: username
|
||||
|
||||
If present, an approval from this username is required. It is
|
||||
treated as a regular expression.
|
||||
|
||||
.. attr:: email
|
||||
|
||||
If present, an approval with this email address is required. It is
|
||||
treated as a regular expression.
|
||||
|
||||
.. attr:: older-than
|
||||
|
||||
If present, the approval must be older than this amount of time
|
||||
to match. Provide a time interval as a number with a suffix of
|
||||
"w" (weeks), "d" (days), "h" (hours), "m" (minutes), "s"
|
||||
(seconds). Example ``48h`` or ``2d``.
|
||||
|
||||
.. attr:: newer-than
|
||||
|
||||
If present, the approval must be newer than this amount
|
||||
of time to match. Same format as "older-than".
|
||||
|
||||
Any other field is interpreted as a review category and value
|
||||
pair. For example ``Verified: 1`` would require that the
|
||||
approval be for a +1 vote in the "Verified" column. The value
|
||||
may either be a single value or a list: ``Verified: [1, 2]``
|
||||
would match either a +1 or +2 vote.
|
||||
|
||||
.. attr:: open
|
||||
|
||||
A boolean value (``true`` or ``false``) that indicates whether
|
||||
the change must be open or closed in order to be rejected.
|
||||
|
||||
.. attr:: current-patchset
|
||||
|
||||
A boolean value (``true`` or ``false``) that indicates whether the
|
||||
change must be the current patchset in order to be rejected.
|
||||
|
||||
.. attr:: wip
|
||||
|
||||
A boolean value (``true`` or ``false``) that indicates whether the
|
||||
change must be wip or not wip in order to be rejected.
|
||||
|
||||
.. attr:: status
|
||||
|
||||
A string value that corresponds with the status of the change
|
||||
reported by Gerrit.
|
||||
|
||||
|
||||
Reference Pipelines Configuration
|
||||
---------------------------------
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Gerrit pipeline triggers now support embedded require and reject
|
||||
filters in order to match. Any conditions set for the pipeline in
|
||||
require or reject filters may also be set for event trigger
|
||||
filters.
|
||||
|
||||
This can be used to construct pipelines which trigger based on
|
||||
certain events but only if certain other conditions are met. It
|
||||
is distinct from pipeline requirements in that it only affects
|
||||
items that are directly enqueued whereas pipeline requirements
|
||||
affect dependencies as well.
|
||||
- |
|
||||
All Gerrit "requires" filters are now available as "reject"
|
||||
filters as well.
|
||||
deprecations:
|
||||
- |
|
||||
The `require-approval` and `reject-approval` Gerrit trigger
|
||||
attributes are deprecated. Use :attr:`pipeline.trigger.<gerrit
|
||||
source>.require` and :attr:`pipeline.trigger.<gerrit
|
||||
source>.reject` instead.
|
|
@ -940,6 +940,7 @@ class FakeGerritChange(object):
|
|||
if self.fail_merge:
|
||||
return
|
||||
self.data['status'] = 'MERGED'
|
||||
self.data['open'] = False
|
||||
self.open = False
|
||||
|
||||
path = os.path.join(self.upstream_root, self.project)
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
- pipeline:
|
||||
name: require-open
|
||||
manager: independent
|
||||
trigger:
|
||||
gerrit:
|
||||
- event: comment-added
|
||||
comment: test require-open
|
||||
require:
|
||||
open: true
|
||||
success:
|
||||
gerrit:
|
||||
Verified: 1
|
||||
|
||||
- pipeline:
|
||||
name: reject-open
|
||||
manager: independent
|
||||
trigger:
|
||||
gerrit:
|
||||
- event: comment-added
|
||||
comment: test reject-open
|
||||
reject:
|
||||
open: true
|
||||
success:
|
||||
gerrit:
|
||||
Verified: 1
|
||||
|
||||
- pipeline:
|
||||
name: require-wip
|
||||
manager: independent
|
||||
trigger:
|
||||
gerrit:
|
||||
- event: comment-added
|
||||
comment: test require-wip
|
||||
require:
|
||||
wip: true
|
||||
success:
|
||||
gerrit:
|
||||
Verified: 1
|
||||
|
||||
- pipeline:
|
||||
name: reject-wip
|
||||
manager: independent
|
||||
trigger:
|
||||
gerrit:
|
||||
- event: comment-added
|
||||
comment: test reject-wip
|
||||
reject:
|
||||
wip: true
|
||||
success:
|
||||
gerrit:
|
||||
Verified: 1
|
||||
|
||||
- pipeline:
|
||||
name: require-current-patchset
|
||||
manager: independent
|
||||
trigger:
|
||||
gerrit:
|
||||
- event: comment-added
|
||||
comment: test require-current-patchset
|
||||
require:
|
||||
current-patchset: true
|
||||
success:
|
||||
gerrit:
|
||||
Verified: 1
|
||||
|
||||
- pipeline:
|
||||
name: reject-current-patchset
|
||||
manager: independent
|
||||
trigger:
|
||||
gerrit:
|
||||
- event: comment-added
|
||||
comment: test reject-current-patchset
|
||||
reject:
|
||||
current-patchset: true
|
||||
success:
|
||||
gerrit:
|
||||
Verified: 1
|
||||
|
||||
- pipeline:
|
||||
name: require-status
|
||||
manager: independent
|
||||
trigger:
|
||||
gerrit:
|
||||
- event: comment-added
|
||||
comment: test require-status
|
||||
require:
|
||||
status: MERGED
|
||||
success:
|
||||
gerrit:
|
||||
Verified: 1
|
||||
|
||||
- pipeline:
|
||||
name: reject-status
|
||||
manager: independent
|
||||
trigger:
|
||||
gerrit:
|
||||
- event: comment-added
|
||||
comment: test reject-status
|
||||
reject:
|
||||
status: MERGED
|
||||
success:
|
||||
gerrit:
|
||||
Verified: 1
|
||||
|
||||
- pipeline:
|
||||
name: require-approval
|
||||
manager: independent
|
||||
trigger:
|
||||
gerrit:
|
||||
- event: comment-added
|
||||
comment: test require-approval
|
||||
require:
|
||||
approval:
|
||||
username: zuul
|
||||
Verified: 1
|
||||
success:
|
||||
gerrit:
|
||||
Verified: 1
|
||||
|
||||
- pipeline:
|
||||
name: reject-approval
|
||||
manager: independent
|
||||
trigger:
|
||||
gerrit:
|
||||
- event: comment-added
|
||||
comment: test reject-approval
|
||||
reject:
|
||||
approval:
|
||||
username: zuul
|
||||
Verified: 1
|
||||
success:
|
||||
gerrit:
|
||||
Verified: 1
|
||||
|
||||
- job:
|
||||
name: base
|
||||
parent: null
|
||||
run: playbooks/base.yaml
|
||||
|
||||
- job: {name: require-open}
|
||||
- job: {name: reject-open}
|
||||
- job: {name: require-wip}
|
||||
- job: {name: reject-wip}
|
||||
- job: {name: require-current-patchset}
|
||||
- job: {name: reject-current-patchset}
|
||||
- job: {name: require-status}
|
||||
- job: {name: reject-status}
|
||||
- job: {name: require-approval}
|
||||
- job: {name: reject-approval}
|
||||
|
||||
- project:
|
||||
name: org/project
|
||||
require-open: {jobs: [require-open]}
|
||||
reject-open: {jobs: [reject-open]}
|
||||
require-wip: {jobs: [require-wip]}
|
||||
reject-wip: {jobs: [reject-wip]}
|
||||
require-current-patchset: {jobs: [require-current-patchset]}
|
||||
reject-current-patchset: {jobs: [reject-current-patchset]}
|
||||
require-status: {jobs: [require-status]}
|
||||
reject-status: {jobs: [reject-status]}
|
||||
require-approval: {jobs: [require-approval]}
|
||||
reject-approval: {jobs: [reject-approval]}
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import time
|
||||
|
||||
from tests.base import ZuulTestCase
|
||||
from tests.base import ZuulTestCase, simple_layout
|
||||
|
||||
|
||||
class TestRequirementsApprovalNewerThan(ZuulTestCase):
|
||||
|
@ -490,3 +490,222 @@ class TestRequirementsTrustedCheck(ZuulTestCase):
|
|||
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
|
||||
self.waitUntilSettled()
|
||||
self.assertHistory([])
|
||||
|
||||
|
||||
class TestGerritTriggerRequirements(ZuulTestCase):
|
||||
scheduler_count = 1
|
||||
|
||||
@simple_layout('layouts/gerrit-trigger-requirements.yaml')
|
||||
def test_require_open(self):
|
||||
# Test trigger require-open
|
||||
jobname = 'require-open'
|
||||
project = 'org/project'
|
||||
A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
|
||||
# A comment event that we will keep submitting to trigger
|
||||
comment = A.getChangeCommentEvent(1, f'test {jobname}')
|
||||
|
||||
# It's open, so it should be enqueued
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
# Not open, so should be ignored
|
||||
A.setMerged()
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
@simple_layout('layouts/gerrit-trigger-requirements.yaml')
|
||||
def test_reject_open(self):
|
||||
# Test trigger reject-open
|
||||
jobname = 'reject-open'
|
||||
project = 'org/project'
|
||||
A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
|
||||
# A comment event that we will keep submitting to trigger
|
||||
comment = A.getChangeCommentEvent(1, f'test {jobname}')
|
||||
|
||||
# It's open, so it should not be enqueued
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 0)
|
||||
|
||||
# Not open, so should be enqueued
|
||||
A.setMerged()
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
@simple_layout('layouts/gerrit-trigger-requirements.yaml')
|
||||
def test_require_wip(self):
|
||||
# Test trigger require-wip
|
||||
jobname = 'require-wip'
|
||||
project = 'org/project'
|
||||
A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
|
||||
# A comment event that we will keep submitting to trigger
|
||||
comment = A.getChangeCommentEvent(1, f'test {jobname}')
|
||||
|
||||
# It's not WIP, so it should be ignored
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 0)
|
||||
|
||||
# WIP, so should be enqueued
|
||||
A.setWorkInProgress(True)
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
@simple_layout('layouts/gerrit-trigger-requirements.yaml')
|
||||
def test_reject_wip(self):
|
||||
# Test trigger reject-wip
|
||||
jobname = 'reject-wip'
|
||||
project = 'org/project'
|
||||
A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
|
||||
# A comment event that we will keep submitting to trigger
|
||||
comment = A.getChangeCommentEvent(1, f'test {jobname}')
|
||||
|
||||
# It's not WIP, so it should be enqueued
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
# WIP, so should be ignored
|
||||
A.setWorkInProgress(True)
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
@simple_layout('layouts/gerrit-trigger-requirements.yaml')
|
||||
def test_require_current_patchset(self):
|
||||
# Test trigger require-current_patchset
|
||||
jobname = 'require-current-patchset'
|
||||
project = 'org/project'
|
||||
A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
|
||||
# A comment event that we will keep submitting to trigger
|
||||
comment = A.getChangeCommentEvent(1, f'test {jobname}')
|
||||
|
||||
# It's current, so it should be enqueued
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
# Not current, so should be ignored
|
||||
A.addPatchset()
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
@simple_layout('layouts/gerrit-trigger-requirements.yaml')
|
||||
def test_reject_current_patchset(self):
|
||||
# Test trigger reject-current_patchset
|
||||
jobname = 'reject-current-patchset'
|
||||
project = 'org/project'
|
||||
A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
|
||||
# A comment event that we will keep submitting to trigger
|
||||
comment = A.getChangeCommentEvent(1, f'test {jobname}')
|
||||
|
||||
# It's current, so it should be ignored
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 0)
|
||||
|
||||
# Not current, so should be enqueued
|
||||
A.addPatchset()
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
@simple_layout('layouts/gerrit-trigger-requirements.yaml')
|
||||
def test_require_status(self):
|
||||
# Test trigger require-status
|
||||
jobname = 'require-status'
|
||||
project = 'org/project'
|
||||
A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
|
||||
# A comment event that we will keep submitting to trigger
|
||||
comment = A.getChangeCommentEvent(1, f'test {jobname}')
|
||||
|
||||
# It's not merged, so it should be ignored
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 0)
|
||||
|
||||
# Merged, so should be enqueued
|
||||
A.setMerged()
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
@simple_layout('layouts/gerrit-trigger-requirements.yaml')
|
||||
def test_reject_status(self):
|
||||
# Test trigger reject-status
|
||||
jobname = 'reject-status'
|
||||
project = 'org/project'
|
||||
A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
|
||||
# A comment event that we will keep submitting to trigger
|
||||
comment = A.getChangeCommentEvent(1, f'test {jobname}')
|
||||
|
||||
# It's not merged, so it should be enqueued
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
# Merged, so should be ignored
|
||||
A.setMerged()
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
@simple_layout('layouts/gerrit-trigger-requirements.yaml')
|
||||
def test_require_approval(self):
|
||||
# Test trigger require-approval
|
||||
jobname = 'require-approval'
|
||||
project = 'org/project'
|
||||
A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
|
||||
# A comment event that we will keep submitting to trigger
|
||||
comment = A.getChangeCommentEvent(1, f'test {jobname}')
|
||||
|
||||
# Missing approval, so it should be ignored
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 0)
|
||||
|
||||
# Has approval, so it should be enqueued
|
||||
A.addApproval('Verified', 1, username='zuul')
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
@simple_layout('layouts/gerrit-trigger-requirements.yaml')
|
||||
def test_reject_approval(self):
|
||||
# Test trigger reject-approval
|
||||
jobname = 'reject-approval'
|
||||
project = 'org/project'
|
||||
A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
|
||||
# A comment event that we will keep submitting to trigger
|
||||
comment = A.getChangeCommentEvent(1, f'test {jobname}')
|
||||
|
||||
# Missing approval, so it should be enqueued
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
||||
# Has approval, so it should be ignored
|
||||
A.addApproval('Verified', 1, username='zuul')
|
||||
self.fake_gerrit.addEvent(comment)
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(len(self.history), 1)
|
||||
self.assertEqual(self.history[0].name, jobname)
|
||||
|
|
|
@ -19,8 +19,8 @@ import urllib.parse
|
|||
import dateutil.parser
|
||||
|
||||
from zuul.model import EventFilter, RefFilter
|
||||
from zuul.model import Change, TriggerEvent
|
||||
from zuul.driver.util import time_to_seconds
|
||||
from zuul.model import Change, TriggerEvent, FalseWithReason
|
||||
from zuul.driver.util import time_to_seconds, to_list
|
||||
from zuul import exceptions
|
||||
|
||||
EMPTY_GIT_REF = '0' * 40 # git sha of all zeros, used during creates/deletes
|
||||
|
@ -243,13 +243,313 @@ class GerritTriggerEvent(TriggerEvent):
|
|||
return 'change-abandoned' == self.type
|
||||
|
||||
|
||||
class GerritApprovalFilter(object):
|
||||
def __init__(self, required_approvals=[], reject_approvals=[]):
|
||||
class GerritEventFilter(EventFilter):
|
||||
def __init__(self, connection_name, trigger, types=[], branches=[],
|
||||
refs=[], event_approvals={}, comments=[], emails=[],
|
||||
usernames=[], required_approvals=[], reject_approvals=[],
|
||||
uuid=None, scheme=None, ignore_deletes=True,
|
||||
require=None, reject=None):
|
||||
|
||||
EventFilter.__init__(self, connection_name, trigger)
|
||||
|
||||
# TODO: Backwards compat, remove after 9.x:
|
||||
if required_approvals and require is None:
|
||||
require = {'approval': required_approvals}
|
||||
if reject_approvals and reject is None:
|
||||
reject = {'approval': reject_approvals}
|
||||
|
||||
if require:
|
||||
self.require_filter = GerritRefFilter.requiresFromConfig(
|
||||
connection_name, require)
|
||||
else:
|
||||
self.require_filter = None
|
||||
|
||||
if reject:
|
||||
self.reject_filter = GerritRefFilter.rejectFromConfig(
|
||||
connection_name, reject)
|
||||
else:
|
||||
self.reject_filter = None
|
||||
|
||||
self._types = types
|
||||
self._branches = branches
|
||||
self._refs = refs
|
||||
self._comments = comments
|
||||
self._emails = emails
|
||||
self._usernames = usernames
|
||||
self.types = [re.compile(x) for x in types]
|
||||
self.branches = [re.compile(x) for x in branches]
|
||||
self.refs = [re.compile(x) for x in refs]
|
||||
self.comments = [re.compile(x) for x in comments]
|
||||
self.emails = [re.compile(x) for x in emails]
|
||||
self.usernames = [re.compile(x) for x in usernames]
|
||||
self.event_approvals = event_approvals
|
||||
self.uuid = uuid
|
||||
self.scheme = scheme
|
||||
self.ignore_deletes = ignore_deletes
|
||||
|
||||
def __repr__(self):
|
||||
ret = '<GerritEventFilter'
|
||||
ret += ' connection: %s' % self.connection_name
|
||||
|
||||
if self._types:
|
||||
ret += ' types: %s' % ', '.join(self._types)
|
||||
if self.uuid:
|
||||
ret += ' uuid: %s' % (self.uuid,)
|
||||
if self.scheme:
|
||||
ret += ' scheme: %s' % (self.scheme,)
|
||||
if self._branches:
|
||||
ret += ' branches: %s' % ', '.join(self._branches)
|
||||
if self._refs:
|
||||
ret += ' refs: %s' % ', '.join(self._refs)
|
||||
if self.ignore_deletes:
|
||||
ret += ' ignore_deletes: %s' % self.ignore_deletes
|
||||
if self.event_approvals:
|
||||
ret += ' event_approvals: %s' % ', '.join(
|
||||
['%s:%s' % a for a in self.event_approvals.items()])
|
||||
if self._comments:
|
||||
ret += ' comments: %s' % ', '.join(self._comments)
|
||||
if self._emails:
|
||||
ret += ' emails: %s' % ', '.join(self._emails)
|
||||
if self._usernames:
|
||||
ret += ' usernames: %s' % ', '.join(self._usernames)
|
||||
if self.require_filter:
|
||||
ret += ' require: %s' % repr(self.require_filter)
|
||||
if self.reject_filter:
|
||||
ret += ' reject: %s' % repr(self.reject_filter)
|
||||
ret += '>'
|
||||
|
||||
return ret
|
||||
|
||||
def matches(self, event, change):
|
||||
if not super().matches(event, change):
|
||||
return False
|
||||
|
||||
# event types are ORed
|
||||
matches_type = False
|
||||
for etype in self.types:
|
||||
if etype.match(event.type):
|
||||
matches_type = True
|
||||
if self.types and not matches_type:
|
||||
return FalseWithReason("Types %s do not match %s" % (
|
||||
self.types, event.type))
|
||||
|
||||
if event.type == 'pending-check':
|
||||
if self.uuid and event.uuid != self.uuid:
|
||||
return False
|
||||
if self.scheme and event.uuid.split(':')[0] != self.scheme:
|
||||
return False
|
||||
|
||||
# branches are ORed
|
||||
matches_branch = False
|
||||
for branch in self.branches:
|
||||
if branch.match(event.branch):
|
||||
matches_branch = True
|
||||
if self.branches and not matches_branch:
|
||||
return FalseWithReason("Branches %s do not match %s" % (
|
||||
self.branches, event.branch))
|
||||
|
||||
# refs are ORed
|
||||
matches_ref = False
|
||||
if event.ref is not None:
|
||||
for ref in self.refs:
|
||||
if ref.match(event.ref):
|
||||
matches_ref = True
|
||||
if self.refs and not matches_ref:
|
||||
return FalseWithReason(
|
||||
"Refs %s do not match %s" % (self.refs, event.ref))
|
||||
if self.ignore_deletes and event.newrev == EMPTY_GIT_REF:
|
||||
# If the updated ref has an empty git sha (all 0s),
|
||||
# then the ref is being deleted
|
||||
return FalseWithReason("Ref deletion events are ignored")
|
||||
|
||||
# comments are ORed
|
||||
matches_comment_re = False
|
||||
for comment_re in self.comments:
|
||||
if (event.comment is not None and
|
||||
comment_re.search(event.comment)):
|
||||
matches_comment_re = True
|
||||
if event.patchsetcomments is not None:
|
||||
for comment in event.patchsetcomments:
|
||||
if (comment is not None and
|
||||
comment_re.search(comment)):
|
||||
matches_comment_re = True
|
||||
if self.comments and not matches_comment_re:
|
||||
return FalseWithReason("Comments %s do not match %s" % (
|
||||
self.comments, event.patchsetcomments))
|
||||
|
||||
# We better have an account provided by Gerrit to do
|
||||
# email filtering.
|
||||
if event.account is not None:
|
||||
account_email = event.account.get('email')
|
||||
# emails are ORed
|
||||
matches_email_re = False
|
||||
for email_re in self.emails:
|
||||
if (account_email is not None and
|
||||
email_re.search(account_email)):
|
||||
matches_email_re = True
|
||||
if self.emails and not matches_email_re:
|
||||
return FalseWithReason("Username %s does not match %s" % (
|
||||
self.emails, account_email))
|
||||
|
||||
# usernames are ORed
|
||||
account_username = event.account.get('username')
|
||||
matches_username_re = False
|
||||
for username_re in self.usernames:
|
||||
if (account_username is not None and
|
||||
username_re.search(account_username)):
|
||||
matches_username_re = True
|
||||
if self.usernames and not matches_username_re:
|
||||
return FalseWithReason("Username %s does not match %s" % (
|
||||
self.usernames, account_username))
|
||||
|
||||
# approvals are ANDed
|
||||
for category, value in self.event_approvals.items():
|
||||
matches_approval = False
|
||||
for eapp in event.approvals:
|
||||
if (eapp['description'] == category and
|
||||
int(eapp['value']) == int(value)):
|
||||
matches_approval = True
|
||||
if not matches_approval:
|
||||
return FalseWithReason("Approvals %s do not match %s" % (
|
||||
self.event_approvals, event.approvals))
|
||||
|
||||
if self.require_filter:
|
||||
require_filter_result = self.require_filter.matches(change)
|
||||
if not require_filter_result:
|
||||
return require_filter_result
|
||||
|
||||
if self.reject_filter:
|
||||
reject_filter_result = self.reject_filter.matches(change)
|
||||
if not reject_filter_result:
|
||||
return reject_filter_result
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class GerritRefFilter(RefFilter):
|
||||
def __init__(self, connection_name,
|
||||
open=None, reject_open=None,
|
||||
current_patchset=None, reject_current_patchset=None,
|
||||
wip=None, reject_wip=None,
|
||||
statuses=[], reject_statuses=[],
|
||||
required_approvals=[], reject_approvals=[]):
|
||||
RefFilter.__init__(self, connection_name)
|
||||
|
||||
self._required_approvals = copy.deepcopy(required_approvals)
|
||||
self.required_approvals = self._tidy_approvals(
|
||||
self._required_approvals)
|
||||
self._reject_approvals = copy.deepcopy(reject_approvals)
|
||||
self.reject_approvals = self._tidy_approvals(self._reject_approvals)
|
||||
self.statuses = statuses
|
||||
self.reject_statuses = reject_statuses
|
||||
|
||||
if reject_open is not None:
|
||||
self.open = not reject_open
|
||||
else:
|
||||
self.open = open
|
||||
if reject_wip is not None:
|
||||
self.wip = not reject_wip
|
||||
else:
|
||||
self.wip = wip
|
||||
if reject_current_patchset is not None:
|
||||
self.current_patchset = not reject_current_patchset
|
||||
else:
|
||||
self.current_patchset = current_patchset
|
||||
|
||||
@classmethod
|
||||
def requiresFromConfig(cls, connection_name, config):
|
||||
return cls(
|
||||
connection_name=connection_name,
|
||||
open=config.get('open'),
|
||||
current_patchset=config.get('current-patchset'),
|
||||
wip=config.get('wip'),
|
||||
statuses=to_list(config.get('status')),
|
||||
required_approvals=to_list(config.get('approval')),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def rejectFromConfig(cls, connection_name, config):
|
||||
return cls(
|
||||
connection_name=connection_name,
|
||||
reject_open=config.get('open'),
|
||||
reject_current_patchset=config.get('current-patchset'),
|
||||
reject_wip=config.get('wip'),
|
||||
reject_statuses=to_list(config.get('status')),
|
||||
reject_approvals=to_list(config.get('approval')),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
ret = '<GerritRefFilter'
|
||||
|
||||
ret += ' connection_name: %s' % self.connection_name
|
||||
if self.open is not None:
|
||||
ret += ' open: %s' % self.open
|
||||
if self.wip is not None:
|
||||
ret += ' wip: %s' % self.wip
|
||||
if self.current_patchset is not None:
|
||||
ret += ' current-patchset: %s' % self.current_patchset
|
||||
if self.statuses:
|
||||
ret += ' statuses: %s' % ', '.join(self.statuses)
|
||||
if self.reject_statuses:
|
||||
ret += ' reject-statuses: %s' % ', '.join(self.reject_statuses)
|
||||
if self.required_approvals:
|
||||
ret += (' required-approvals: %s' %
|
||||
str(self.required_approvals))
|
||||
if self.reject_approvals:
|
||||
ret += (' reject-approvals: %s' %
|
||||
str(self.reject_approvals))
|
||||
ret += '>'
|
||||
|
||||
return ret
|
||||
|
||||
def matches(self, change):
|
||||
if self.open is not None:
|
||||
# if a "change" has no number, it's not a change, but a push
|
||||
# and cannot possibly pass this test.
|
||||
if hasattr(change, 'number'):
|
||||
if self.open != change.open:
|
||||
return FalseWithReason(
|
||||
"Change does not match open requirement")
|
||||
else:
|
||||
return FalseWithReason("Ref is not a Change")
|
||||
|
||||
if self.current_patchset is not None:
|
||||
# if a "change" has no number, it's not a change, but a push
|
||||
# and cannot possibly pass this test.
|
||||
if hasattr(change, 'number'):
|
||||
if self.current_patchset != change.is_current_patchset:
|
||||
return FalseWithReason(
|
||||
"Change does not match current patchset requirement")
|
||||
else:
|
||||
return FalseWithReason("Ref is not a Change")
|
||||
|
||||
if self.wip is not None:
|
||||
# if a "change" has no number, it's not a change, but a push
|
||||
# and cannot possibly pass this test.
|
||||
if hasattr(change, 'number'):
|
||||
if self.wip != change.wip:
|
||||
return FalseWithReason(
|
||||
"Change does not match WIP requirement")
|
||||
else:
|
||||
return FalseWithReason("Ref is not a Change")
|
||||
|
||||
if self.statuses:
|
||||
if change.status not in self.statuses:
|
||||
return FalseWithReason(
|
||||
"Required statuses %s do not match %s" % (
|
||||
self.statuses, change.status))
|
||||
if self.reject_statuses:
|
||||
if change.status in self.reject_statuses:
|
||||
return FalseWithReason(
|
||||
"Reject statuses %s match %s" % (
|
||||
self.reject_statuses, change.status))
|
||||
|
||||
# required approvals are ANDed (reject approvals are ORed)
|
||||
matches_approvals_result = self.matchesApprovals(change)
|
||||
if not matches_approvals_result:
|
||||
return matches_approvals_result
|
||||
|
||||
return True
|
||||
|
||||
def _tidy_approvals(self, approvals):
|
||||
for a in approvals:
|
||||
|
@ -297,10 +597,11 @@ class GerritApprovalFilter(object):
|
|||
if self.required_approvals or self.reject_approvals:
|
||||
if not hasattr(change, 'number'):
|
||||
# Not a change, no reviews
|
||||
return False
|
||||
return FalseWithReason("Ref is not a Change")
|
||||
if self.required_approvals and not change.approvals:
|
||||
# A change with no approvals can not match
|
||||
return False
|
||||
return FalseWithReason("Approvals %s does not match %s" % (
|
||||
self.required_approvals, change.approvals))
|
||||
|
||||
# TODO(jhesketh): If we wanted to optimise this slightly we could
|
||||
# analyse both the REQUIRE and REJECT filters by looping over the
|
||||
|
@ -320,7 +621,9 @@ class GerritApprovalFilter(object):
|
|||
matches_rapproval = True
|
||||
break
|
||||
if not matches_rapproval:
|
||||
return False
|
||||
return FalseWithReason(
|
||||
"Required approvals %s do not match %s" % (
|
||||
self.required_approvals, change.approvals))
|
||||
return True
|
||||
|
||||
def matchesNoRejectApprovals(self, change):
|
||||
|
@ -330,236 +633,8 @@ class GerritApprovalFilter(object):
|
|||
if self._match_approval_required_approval(rapproval, approval):
|
||||
# A reject approval has been matched, so we reject
|
||||
# immediately
|
||||
return False
|
||||
return FalseWithReason("Reject approvals %s match %s" % (
|
||||
self.reject_approvals, change.approvals))
|
||||
# To get here no rejects can have been matched so we should be good to
|
||||
# queue
|
||||
return True
|
||||
|
||||
|
||||
class GerritEventFilter(EventFilter, GerritApprovalFilter):
|
||||
def __init__(self, connection_name, trigger, types=[], branches=[],
|
||||
refs=[], event_approvals={}, comments=[], emails=[],
|
||||
usernames=[], required_approvals=[], reject_approvals=[],
|
||||
uuid=None, scheme=None, ignore_deletes=True):
|
||||
|
||||
EventFilter.__init__(self, connection_name, trigger)
|
||||
|
||||
GerritApprovalFilter.__init__(self,
|
||||
required_approvals=required_approvals,
|
||||
reject_approvals=reject_approvals)
|
||||
|
||||
self._types = types
|
||||
self._branches = branches
|
||||
self._refs = refs
|
||||
self._comments = comments
|
||||
self._emails = emails
|
||||
self._usernames = usernames
|
||||
self.types = [re.compile(x) for x in types]
|
||||
self.branches = [re.compile(x) for x in branches]
|
||||
self.refs = [re.compile(x) for x in refs]
|
||||
self.comments = [re.compile(x) for x in comments]
|
||||
self.emails = [re.compile(x) for x in emails]
|
||||
self.usernames = [re.compile(x) for x in usernames]
|
||||
self.event_approvals = event_approvals
|
||||
self.uuid = uuid
|
||||
self.scheme = scheme
|
||||
self.ignore_deletes = ignore_deletes
|
||||
|
||||
def __repr__(self):
|
||||
ret = '<GerritEventFilter'
|
||||
ret += ' connection: %s' % self.connection_name
|
||||
|
||||
if self._types:
|
||||
ret += ' types: %s' % ', '.join(self._types)
|
||||
if self.uuid:
|
||||
ret += ' uuid: %s' % (self.uuid,)
|
||||
if self.scheme:
|
||||
ret += ' scheme: %s' % (self.scheme,)
|
||||
if self._branches:
|
||||
ret += ' branches: %s' % ', '.join(self._branches)
|
||||
if self._refs:
|
||||
ret += ' refs: %s' % ', '.join(self._refs)
|
||||
if self.ignore_deletes:
|
||||
ret += ' ignore_deletes: %s' % self.ignore_deletes
|
||||
if self.event_approvals:
|
||||
ret += ' event_approvals: %s' % ', '.join(
|
||||
['%s:%s' % a for a in self.event_approvals.items()])
|
||||
if self.required_approvals:
|
||||
ret += ' required_approvals: %s' % ', '.join(
|
||||
['%s' % a for a in self._required_approvals])
|
||||
if self.reject_approvals:
|
||||
ret += ' reject_approvals: %s' % ', '.join(
|
||||
['%s' % a for a in self._reject_approvals])
|
||||
if self._comments:
|
||||
ret += ' comments: %s' % ', '.join(self._comments)
|
||||
if self._emails:
|
||||
ret += ' emails: %s' % ', '.join(self._emails)
|
||||
if self._usernames:
|
||||
ret += ' usernames: %s' % ', '.join(self._usernames)
|
||||
ret += '>'
|
||||
|
||||
return ret
|
||||
|
||||
def matches(self, event, change):
|
||||
if not super().matches(event, change):
|
||||
return False
|
||||
|
||||
# event types are ORed
|
||||
matches_type = False
|
||||
for etype in self.types:
|
||||
if etype.match(event.type):
|
||||
matches_type = True
|
||||
if self.types and not matches_type:
|
||||
return False
|
||||
|
||||
if event.type == 'pending-check':
|
||||
if self.uuid and event.uuid != self.uuid:
|
||||
return False
|
||||
if self.scheme and event.uuid.split(':')[0] != self.scheme:
|
||||
return False
|
||||
|
||||
# branches are ORed
|
||||
matches_branch = False
|
||||
for branch in self.branches:
|
||||
if branch.match(event.branch):
|
||||
matches_branch = True
|
||||
if self.branches and not matches_branch:
|
||||
return False
|
||||
|
||||
# refs are ORed
|
||||
matches_ref = False
|
||||
if event.ref is not None:
|
||||
for ref in self.refs:
|
||||
if ref.match(event.ref):
|
||||
matches_ref = True
|
||||
if self.refs and not matches_ref:
|
||||
return False
|
||||
if self.ignore_deletes and event.newrev == EMPTY_GIT_REF:
|
||||
# If the updated ref has an empty git sha (all 0s),
|
||||
# then the ref is being deleted
|
||||
return False
|
||||
|
||||
# comments are ORed
|
||||
matches_comment_re = False
|
||||
for comment_re in self.comments:
|
||||
if (event.comment is not None and
|
||||
comment_re.search(event.comment)):
|
||||
matches_comment_re = True
|
||||
if event.patchsetcomments is not None:
|
||||
for comment in event.patchsetcomments:
|
||||
if (comment is not None and
|
||||
comment_re.search(comment)):
|
||||
matches_comment_re = True
|
||||
if self.comments and not matches_comment_re:
|
||||
return False
|
||||
|
||||
# We better have an account provided by Gerrit to do
|
||||
# email filtering.
|
||||
if event.account is not None:
|
||||
account_email = event.account.get('email')
|
||||
# emails are ORed
|
||||
matches_email_re = False
|
||||
for email_re in self.emails:
|
||||
if (account_email is not None and
|
||||
email_re.search(account_email)):
|
||||
matches_email_re = True
|
||||
if self.emails and not matches_email_re:
|
||||
return False
|
||||
|
||||
# usernames are ORed
|
||||
account_username = event.account.get('username')
|
||||
matches_username_re = False
|
||||
for username_re in self.usernames:
|
||||
if (account_username is not None and
|
||||
username_re.search(account_username)):
|
||||
matches_username_re = True
|
||||
if self.usernames and not matches_username_re:
|
||||
return False
|
||||
|
||||
# approvals are ANDed
|
||||
for category, value in self.event_approvals.items():
|
||||
matches_approval = False
|
||||
for eapp in event.approvals:
|
||||
if (eapp['description'] == category and
|
||||
int(eapp['value']) == int(value)):
|
||||
matches_approval = True
|
||||
if not matches_approval:
|
||||
return False
|
||||
|
||||
# required approvals are ANDed (reject approvals are ORed)
|
||||
if not self.matchesApprovals(change):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class GerritRefFilter(RefFilter, GerritApprovalFilter):
|
||||
def __init__(self, connection_name, open=None, current_patchset=None,
|
||||
wip=None, statuses=[], required_approvals=[],
|
||||
reject_approvals=[]):
|
||||
RefFilter.__init__(self, connection_name)
|
||||
|
||||
GerritApprovalFilter.__init__(self,
|
||||
required_approvals=required_approvals,
|
||||
reject_approvals=reject_approvals)
|
||||
|
||||
self.open = open
|
||||
self.wip = wip
|
||||
self.current_patchset = current_patchset
|
||||
self.statuses = statuses
|
||||
|
||||
def __repr__(self):
|
||||
ret = '<GerritRefFilter'
|
||||
|
||||
ret += ' connection_name: %s' % self.connection_name
|
||||
if self.open is not None:
|
||||
ret += ' open: %s' % self.open
|
||||
if self.current_patchset is not None:
|
||||
ret += ' current-patchset: %s' % self.current_patchset
|
||||
if self.statuses:
|
||||
ret += ' statuses: %s' % ', '.join(self.statuses)
|
||||
if self.required_approvals:
|
||||
ret += (' required-approvals: %s' %
|
||||
str(self.required_approvals))
|
||||
if self.reject_approvals:
|
||||
ret += (' reject-approvals: %s' %
|
||||
str(self.reject_approvals))
|
||||
ret += '>'
|
||||
|
||||
return ret
|
||||
|
||||
def matches(self, change):
|
||||
|
||||
filters = [
|
||||
{
|
||||
"required": self.open,
|
||||
"value": change.open
|
||||
},
|
||||
{
|
||||
"required": self.current_patchset,
|
||||
"value": change.is_current_patchset
|
||||
},
|
||||
{
|
||||
"required": self.wip,
|
||||
"value": change.wip
|
||||
},
|
||||
]
|
||||
configured = filter(lambda x: x["required"] is not None, filters)
|
||||
|
||||
# if a "change" has no number, it's not a change, but a push
|
||||
# and cannot possibly pass this test.
|
||||
if hasattr(change, 'number'):
|
||||
if any(map(lambda x: x["required"] != x["value"], configured)):
|
||||
return False
|
||||
elif configured:
|
||||
return False
|
||||
|
||||
if self.statuses:
|
||||
if change.status not in self.statuses:
|
||||
return False
|
||||
|
||||
# required approvals are ANDed (reject approvals are ORed)
|
||||
if not self.matchesApprovals(change):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
@ -20,7 +20,7 @@ from urllib.parse import urlparse
|
|||
from zuul.source import BaseSource
|
||||
from zuul.model import Project
|
||||
from zuul.driver.gerrit.gerritmodel import GerritRefFilter
|
||||
from zuul.driver.util import scalar_or_list, to_list
|
||||
from zuul.driver.util import scalar_or_list
|
||||
from zuul.lib.dependson import find_dependency_headers
|
||||
from zuul.zk.change_cache import ChangeKey
|
||||
|
||||
|
@ -206,21 +206,15 @@ class GerritSource(BaseSource):
|
|||
return self.connection._getGitwebUrl(project, sha)
|
||||
|
||||
def getRequireFilters(self, config):
|
||||
f = GerritRefFilter(
|
||||
connection_name=self.connection.connection_name,
|
||||
open=config.get('open'),
|
||||
current_patchset=config.get('current-patchset'),
|
||||
wip=config.get('wip'),
|
||||
statuses=to_list(config.get('status')),
|
||||
required_approvals=to_list(config.get('approval')),
|
||||
)
|
||||
f = GerritRefFilter.requiresFromConfig(
|
||||
self.connection.connection_name,
|
||||
config)
|
||||
return [f]
|
||||
|
||||
def getRejectFilters(self, config):
|
||||
f = GerritRefFilter(
|
||||
connection_name=self.connection.connection_name,
|
||||
reject_approvals=to_list(config.get('approval')),
|
||||
)
|
||||
f = GerritRefFilter.rejectFromConfig(
|
||||
self.connection.connection_name,
|
||||
config)
|
||||
return [f]
|
||||
|
||||
def getRefForChange(self, change):
|
||||
|
@ -244,11 +238,13 @@ def getRequireSchema():
|
|||
'current-patchset': bool,
|
||||
'wip': bool,
|
||||
'status': scalar_or_list(str)}
|
||||
|
||||
return require
|
||||
|
||||
|
||||
def getRejectSchema():
|
||||
reject = {'approval': scalar_or_list(approval)}
|
||||
|
||||
reject = {'approval': scalar_or_list(approval),
|
||||
'open': bool,
|
||||
'current-patchset': bool,
|
||||
'wip': bool,
|
||||
'status': scalar_or_list(str)}
|
||||
return reject
|
||||
|
|
|
@ -16,6 +16,7 @@ import logging
|
|||
import voluptuous as v
|
||||
from zuul.trigger import BaseTrigger
|
||||
from zuul.driver.gerrit.gerritmodel import GerritEventFilter
|
||||
from zuul.driver.gerrit import gerritsource
|
||||
from zuul.driver.util import scalar_or_list, to_list
|
||||
|
||||
|
||||
|
@ -59,7 +60,9 @@ class GerritTrigger(BaseTrigger):
|
|||
),
|
||||
uuid=trigger.get('uuid'),
|
||||
scheme=trigger.get('scheme'),
|
||||
ignore_deletes=ignore_deletes
|
||||
ignore_deletes=ignore_deletes,
|
||||
require=trigger.get('require'),
|
||||
reject=trigger.get('reject'),
|
||||
)
|
||||
efilters.append(f)
|
||||
|
||||
|
@ -101,6 +104,8 @@ def getSchema():
|
|||
'approval': scalar_or_list(variable_dict),
|
||||
'require-approval': scalar_or_list(approval),
|
||||
'reject-approval': scalar_or_list(approval),
|
||||
'require': gerritsource.getRequireSchema(),
|
||||
'reject': gerritsource.getRejectSchema(),
|
||||
}
|
||||
|
||||
return gerrit_trigger
|
||||
|
|
|
@ -255,7 +255,7 @@ class GithubEventFilter(EventFilter):
|
|||
if etype.match(event.type):
|
||||
matches_type = True
|
||||
if self.types and not matches_type:
|
||||
return FalseWithReason("Types %s doesn't match %s" % (
|
||||
return FalseWithReason("Types %s do not match %s" % (
|
||||
self.types, event.type))
|
||||
|
||||
# branches are ORed
|
||||
|
@ -264,7 +264,7 @@ class GithubEventFilter(EventFilter):
|
|||
if branch.match(event.branch):
|
||||
matches_branch = True
|
||||
if self.branches and not matches_branch:
|
||||
return FalseWithReason("Branches %s doesn't match %s" % (
|
||||
return FalseWithReason("Branches %s do not match %s" % (
|
||||
self.branches, event.branch))
|
||||
|
||||
# refs are ORed
|
||||
|
@ -275,11 +275,11 @@ class GithubEventFilter(EventFilter):
|
|||
matches_ref = True
|
||||
if self.refs and not matches_ref:
|
||||
return FalseWithReason(
|
||||
"Refs %s doesn't match %s" % (self.refs, event.ref))
|
||||
"Refs %s do not match %s" % (self.refs, event.ref))
|
||||
if self.ignore_deletes and event.newrev == EMPTY_GIT_REF:
|
||||
# If the updated ref has an empty git sha (all 0s),
|
||||
# then the ref is being deleted
|
||||
return FalseWithReason("Ref deletion are ignored")
|
||||
return FalseWithReason("Ref deletion events are ignored")
|
||||
|
||||
# comments are ORed
|
||||
matches_comment_re = False
|
||||
|
@ -288,7 +288,7 @@ class GithubEventFilter(EventFilter):
|
|||
comment_re.search(event.comment)):
|
||||
matches_comment_re = True
|
||||
if self.comments and not matches_comment_re:
|
||||
return FalseWithReason("Comments %s doesn't match %s" % (
|
||||
return FalseWithReason("Comments %s do not match %s" % (
|
||||
self.comments, event.comment))
|
||||
|
||||
# actions are ORed
|
||||
|
@ -297,7 +297,7 @@ class GithubEventFilter(EventFilter):
|
|||
if (event.action == action):
|
||||
matches_action = True
|
||||
if self.actions and not matches_action:
|
||||
return FalseWithReason("Actions %s doesn't match %s" % (
|
||||
return FalseWithReason("Actions %s do not match %s" % (
|
||||
self.actions, event.action))
|
||||
|
||||
# check_runs are ORed
|
||||
|
@ -308,22 +308,22 @@ class GithubEventFilter(EventFilter):
|
|||
check_run_found = True
|
||||
break
|
||||
if not check_run_found:
|
||||
return FalseWithReason("Check_runs %s doesn't match %s" % (
|
||||
return FalseWithReason("Check runs %s do not match %s" % (
|
||||
self.check_runs, event.check_run))
|
||||
|
||||
# labels are ORed
|
||||
if self.labels and event.label not in self.labels:
|
||||
return FalseWithReason("Labels %s doesn't match %s" % (
|
||||
return FalseWithReason("Labels %s do not match %s" % (
|
||||
self.labels, event.label))
|
||||
|
||||
# unlabels are ORed
|
||||
if self.unlabels and event.unlabel not in self.unlabels:
|
||||
return FalseWithReason("Unlabels %s doesn't match %s" % (
|
||||
return FalseWithReason("Unlabels %s do not match %s" % (
|
||||
self.unlabels, event.unlabel))
|
||||
|
||||
# states are ORed
|
||||
if self.states and event.state not in self.states:
|
||||
return FalseWithReason("States %s doesn't match %s" % (
|
||||
return FalseWithReason("States %s do not match %s" % (
|
||||
self.states, event.state))
|
||||
|
||||
# statuses are ORed
|
||||
|
@ -334,7 +334,7 @@ class GithubEventFilter(EventFilter):
|
|||
status_found = True
|
||||
break
|
||||
if not status_found:
|
||||
return FalseWithReason("Statuses %s doesn't match %s" % (
|
||||
return FalseWithReason("Statuses %s do not match %s" % (
|
||||
self.statuses, event.status))
|
||||
|
||||
if self.require_filter:
|
||||
|
@ -498,7 +498,7 @@ class GithubRefFilter(RefFilter):
|
|||
if self.required_reviews and not change.reviews:
|
||||
# No reviews means no matching of required bits
|
||||
# having reject reviews but no reviews on the change is okay
|
||||
return FalseWithReason("Reviews %s does not match %s" % (
|
||||
return FalseWithReason("Reviews %s do not match %s" % (
|
||||
self.required_reviews, change.reviews))
|
||||
|
||||
return (self.matchesRequiredReviews(change) and
|
||||
|
@ -514,7 +514,7 @@ class GithubRefFilter(RefFilter):
|
|||
break
|
||||
if not matches_review:
|
||||
return FalseWithReason(
|
||||
"Required reviews %s does not match %s" % (
|
||||
"Required reviews %s do not match %s" % (
|
||||
self.required_reviews, change.reviews))
|
||||
return True
|
||||
|
||||
|
@ -523,7 +523,7 @@ class GithubRefFilter(RefFilter):
|
|||
for review in change.reviews:
|
||||
if self._match_review_required_review(rreview, review):
|
||||
# A review matched, we can reject right away
|
||||
return FalseWithReason("Reject reviews %s matches %s" % (
|
||||
return FalseWithReason("Reject reviews %s match %s" % (
|
||||
self.reject_reviews, change.reviews))
|
||||
return True
|
||||
|
||||
|
@ -531,10 +531,10 @@ class GithubRefFilter(RefFilter):
|
|||
if self.required_statuses or self.reject_statuses:
|
||||
if not hasattr(change, 'number'):
|
||||
# not a PR, no status
|
||||
return FalseWithReason("Can't match statuses without PR")
|
||||
return FalseWithReason("Can not match statuses without PR")
|
||||
if self.required_statuses and not change.status:
|
||||
return FalseWithReason(
|
||||
"Required statuses %s does not match %s" % (
|
||||
"Required statuses %s do not match %s" % (
|
||||
self.required_statuses, change.status))
|
||||
required_statuses_results = self.matchesRequiredStatuses(change)
|
||||
if not required_statuses_results:
|
||||
|
@ -551,7 +551,7 @@ class GithubRefFilter(RefFilter):
|
|||
for status in change.status:
|
||||
if re2.fullmatch(required_status, status):
|
||||
return True
|
||||
return FalseWithReason("RequiredStatuses %s does not match %s" % (
|
||||
return FalseWithReason("Required statuses %s do not match %s" % (
|
||||
self.required_statuses, change.status))
|
||||
return True
|
||||
|
||||
|
@ -561,7 +561,7 @@ class GithubRefFilter(RefFilter):
|
|||
for rstatus in self.reject_statuses:
|
||||
for status in change.status:
|
||||
if re2.fullmatch(rstatus, status):
|
||||
return FalseWithReason("NoRejectStatuses %s matches %s" % (
|
||||
return FalseWithReason("Reject statuses %s match %s" % (
|
||||
self.reject_statuses, change.status))
|
||||
return True
|
||||
|
||||
|
@ -569,7 +569,7 @@ class GithubRefFilter(RefFilter):
|
|||
if self.required_labels or self.reject_labels:
|
||||
if not hasattr(change, 'number'):
|
||||
# not a PR, no label
|
||||
return FalseWithReason("Can't match labels without PR")
|
||||
return FalseWithReason("Can not match labels without PR")
|
||||
if self.required_labels and not change.labels:
|
||||
# No labels means no matching of required bits
|
||||
# having reject labels but no labels on the change is okay
|
||||
|
@ -582,14 +582,14 @@ class GithubRefFilter(RefFilter):
|
|||
def matchesRequiredLabels(self, change):
|
||||
for label in self.required_labels:
|
||||
if label not in change.labels:
|
||||
return FalseWithReason("Labels %s does not match %s" % (
|
||||
return FalseWithReason("Labels %s do not match %s" % (
|
||||
self.required_labels, change.labels))
|
||||
return True
|
||||
|
||||
def matchesNoRejectLabels(self, change):
|
||||
for label in self.reject_labels:
|
||||
if label in change.labels:
|
||||
return FalseWithReason("NoRejectLabels %s matches %s" % (
|
||||
return FalseWithReason("Reject labels %s match %s" % (
|
||||
self.reject_labels, change.labels))
|
||||
return True
|
||||
|
||||
|
|
Loading…
Reference in New Issue