Merge "Filter events on event connection"

This commit is contained in:
Zuul 2021-05-10 21:49:41 +00:00 committed by Gerrit Code Review
commit ba7b146fd1
26 changed files with 122 additions and 36 deletions

View File

@ -0,0 +1,5 @@
---
fixes:
- |
Fixed a bug where multiple connections of the same type would not filter
trigger events coming from the wrong connection.

View File

@ -30,8 +30,10 @@
trigger:
another_gerrit:
- event: patchset-created
branch: 'master'
review_gerrit:
- event: patchset-created
branch: 'develop'
success:
review_gerrit:
Verified: 1

View File

@ -493,6 +493,16 @@ class TestMultipleGerrits(ZuulTestCase):
def test_multiple_project_separate_gerrits_common_pipeline(self):
self.executor_server.hold_jobs_in_build = True
self.create_branch('org/project2', 'develop')
self.fake_another_gerrit.addEvent(
self.fake_another_gerrit.getFakeBranchCreatedEvent(
'org/project2', 'develop'))
self.fake_another_gerrit.addEvent(
self.fake_review_gerrit.getFakeBranchCreatedEvent(
'org/project2', 'develop'))
self.waitUntilSettled()
A = self.fake_another_gerrit.addFakeChange(
'org/project2', 'master', 'A')
self.fake_another_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
@ -512,7 +522,7 @@ class TestMultipleGerrits(ZuulTestCase):
self.fake_review_gerrit.change_number = 50
B = self.fake_review_gerrit.addFakeChange(
'org/project2', 'master', 'B')
'org/project2', 'develop', 'B')
self.fake_review_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
@ -528,6 +538,25 @@ class TestMultipleGerrits(ZuulTestCase):
pipeline='common_check'),
])
# NOTE(avass): This last change should not trigger any pipelines since
# common_check is configured to only run on master for another_gerrit
C = self.fake_another_gerrit.addFakeChange(
'org/project2', 'develop', 'C')
self.fake_another_gerrit.addEvent(C.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertBuilds([
dict(name='project-test2',
changes='1,1',
project='org/project2',
pipeline='common_check'),
dict(name='project-test1',
changes='51,1',
project='org/project2',
pipeline='common_check'),
])
self.executor_server.hold_jobs_in_build = False
self.executor_server.release()
self.waitUntilSettled()

View File

@ -1368,15 +1368,16 @@ class PipelineParser(object):
manager.ref_filters.extend(
source.getRejectFilters(reject_config))
for trigger_name, trigger_config in conf.get('trigger').items():
for connection_name, trigger_config in conf.get('trigger').items():
if self.pcontext.tenant.allowed_triggers is not None and \
trigger_name not in self.pcontext.tenant.allowed_triggers:
raise UnknownConnection(trigger_name)
connection_name not in self.pcontext.tenant.allowed_triggers:
raise UnknownConnection(connection_name)
trigger = self.pcontext.connections.getTrigger(
trigger_name, trigger_config)
connection_name, trigger_config)
pipeline.triggers.append(trigger)
manager.event_filters.extend(
trigger.getEventFilters(conf['trigger'][trigger_name]))
trigger.getEventFilters(connection_name,
conf['trigger'][connection_name]))
# Pipelines don't get frozen
return pipeline

View File

@ -179,6 +179,7 @@ class GerritEventConnector(threading.Thread):
time.sleep(max((timestamp + self.delay) - now, 0.0))
event = GerritTriggerEvent()
event.timestamp = timestamp
event.connection_name = self.connection.connection_name
# Gerrit events don't have an event id that could be used to globally
# identify this event in the system so we have to generate one.

View File

@ -295,12 +295,12 @@ class GerritApprovalFilter(object):
class GerritEventFilter(EventFilter, GerritApprovalFilter):
def __init__(self, trigger, types=[], branches=[], refs=[],
event_approvals={}, comments=[], emails=[], usernames=[],
required_approvals=[], reject_approvals=[], uuid=None,
scheme=None, ignore_deletes=True):
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, trigger)
EventFilter.__init__(self, connection_name, trigger)
GerritApprovalFilter.__init__(self,
required_approvals=required_approvals,
@ -325,6 +325,7 @@ class GerritEventFilter(EventFilter, GerritApprovalFilter):
def __repr__(self):
ret = '<GerritEventFilter'
ret += ' connection: %s' % self.connection_name
if self._types:
ret += ' types: %s' % ', '.join(self._types)
@ -358,6 +359,9 @@ class GerritEventFilter(EventFilter, GerritApprovalFilter):
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:

View File

@ -23,7 +23,7 @@ class GerritTrigger(BaseTrigger):
name = 'gerrit'
log = logging.getLogger("zuul.GerritTrigger")
def getEventFilters(self, trigger_conf):
def getEventFilters(self, connection_name, trigger_conf):
efilters = []
for trigger in to_list(trigger_conf):
approvals = {}
@ -42,6 +42,7 @@ class GerritTrigger(BaseTrigger):
usernames = to_list(trigger.get('username_filter'))
ignore_deletes = trigger.get('ignore-deletes', True)
f = GerritEventFilter(
connection_name=connection_name,
trigger=self,
types=to_list(trigger['event']),
branches=to_list(trigger.get('branch')),

View File

@ -137,6 +137,7 @@ class GitConnection(BaseConnection):
def watcherCallback(self, data):
event = GitTriggerEvent()
event.connection_name = self.connection_name
event.type = 'ref-updated'
event.timestamp = time.time()
event.project_hostname = self.canonical_hostname

View File

@ -40,10 +40,10 @@ class GitTriggerEvent(TriggerEvent):
class GitEventFilter(EventFilter):
def __init__(self, trigger, types=None, refs=None,
def __init__(self, connection_name, trigger, types=None, refs=None,
ignore_deletes=True):
super().__init__(trigger)
super().__init__(connection_name, trigger)
self._refs = refs
self.types = types if types is not None else []
@ -53,6 +53,7 @@ class GitEventFilter(EventFilter):
def __repr__(self):
ret = '<GitEventFilter'
ret += ' connection: %s' % self.connection_name
if self.types:
ret += ' types: %s' % ', '.join(self.types)
@ -65,6 +66,9 @@ class GitEventFilter(EventFilter):
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:

View File

@ -23,10 +23,11 @@ class GitTrigger(BaseTrigger):
name = 'git'
log = logging.getLogger("zuul.GitTrigger")
def getEventFilters(self, trigger_conf):
def getEventFilters(self, connection_name, trigger_conf):
efilters = []
for trigger in to_list(trigger_conf):
f = GitEventFilter(
connection_name=connection_name,
trigger=self,
types=to_list(trigger['event']),
refs=to_list(trigger.get('ref')),

View File

@ -406,6 +406,7 @@ class GithubEventProcessor(object):
base_repo = self.body.get('repository')
event = GithubTriggerEvent()
event.connection_name = self.connection.connection_name
event.trigger_name = 'github'
event.project_name = base_repo.get('full_name')
event.type = 'push'
@ -615,6 +616,7 @@ class GithubEventProcessor(object):
def _pull_request_to_event(self, pr_body):
event = GithubTriggerEvent()
event.connection_name = self.connection.connection_name
event.trigger_name = 'github'
base = pr_body.get('base')

View File

@ -260,12 +260,12 @@ class GithubCommonFilter(object):
class GithubEventFilter(EventFilter, GithubCommonFilter):
def __init__(self, trigger, types=[], branches=[], refs=[],
comments=[], actions=[], labels=[], unlabels=[],
def __init__(self, connection_name, trigger, types=[], branches=[],
refs=[], comments=[], actions=[], labels=[], unlabels=[],
states=[], statuses=[], required_statuses=[],
check_runs=[], ignore_deletes=True):
EventFilter.__init__(self, trigger)
EventFilter.__init__(self, connection_name, trigger)
GithubCommonFilter.__init__(self, required_statuses=required_statuses)
@ -288,7 +288,7 @@ class GithubEventFilter(EventFilter, GithubCommonFilter):
def __repr__(self):
ret = '<GithubEventFilter'
ret += ' connection: %s' % self.connection_name
if self._types:
ret += ' types: %s' % ', '.join(self._types)
if self._branches:
@ -318,6 +318,9 @@ class GithubEventFilter(EventFilter, GithubCommonFilter):
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:

View File

@ -23,10 +23,11 @@ class GithubTrigger(BaseTrigger):
name = 'github'
log = logging.getLogger("zuul.trigger.GithubTrigger")
def getEventFilters(self, trigger_config):
def getEventFilters(self, connection_name, trigger_config):
efilters = []
for trigger in to_list(trigger_config):
f = GithubEventFilter(
connection_name=connection_name,
trigger=self,
types=to_list(trigger['event']),
actions=to_list(trigger.get('action')),

View File

@ -86,6 +86,7 @@ class GitlabEventConnector(threading.Thread):
def _event_base(self, body):
event = GitlabTriggerEvent()
event.connection_name = self.connection.connection_name
attrs = body.get('object_attributes')
if attrs:
event.updated_at = int(dateutil.parser.parse(

View File

@ -103,9 +103,9 @@ class GitlabTriggerEvent(TriggerEvent):
class GitlabEventFilter(EventFilter):
def __init__(
self, trigger, types=None, actions=None,
self, connection_name, trigger, types=None, actions=None,
comments=None, refs=None, labels=None, ignore_deletes=True):
super(GitlabEventFilter, self).__init__(self)
super().__init__(connection_name, trigger)
self._types = types or []
self.types = [re.compile(x) for x in self._types]
self.actions = actions or []
@ -118,6 +118,7 @@ class GitlabEventFilter(EventFilter):
def __repr__(self):
ret = '<GitlabEventFilter'
ret += ' connection: %s' % self.connection_name
if self._types:
ret += ' types: %s' % ', '.join(self._types)
@ -136,6 +137,9 @@ class GitlabEventFilter(EventFilter):
return ret
def matches(self, event, change):
if not super().matches(event, change):
return False
matches_type = False
for etype in self.types:
if etype.match(event.type):

View File

@ -23,10 +23,11 @@ class GitlabTrigger(BaseTrigger):
name = 'gitlab'
log = logging.getLogger("zuul.trigger.GitlabTrigger")
def getEventFilters(self, trigger_config):
def getEventFilters(self, connection_name, trigger_config):
efilters = []
for trigger in to_list(trigger_config):
f = GitlabEventFilter(
connection_name=connection_name,
trigger=self,
types=to_list(trigger['event']),
actions=to_list(trigger.get('action')),

View File

@ -203,6 +203,7 @@ class PagureEventConnector(threading.Thread):
def _event_base(self, body, pull_data_field='pullrequest'):
event = PagureTriggerEvent()
event.connection_name = self.connection.connection_name
if pull_data_field in body['msg']:
data = body['msg'][pull_data_field]

View File

@ -111,10 +111,11 @@ class PagureTriggerEvent(TriggerEvent):
class PagureEventFilter(EventFilter):
def __init__(self, trigger, types=[], refs=[], statuses=[],
comments=[], actions=[], tags=[], ignore_deletes=True):
def __init__(self, connection_name, trigger, types=[], refs=[],
statuses=[], comments=[], actions=[], tags=[],
ignore_deletes=True):
EventFilter.__init__(self, trigger)
EventFilter.__init__(self, connection_name, trigger)
self._types = types
self._refs = refs
@ -129,6 +130,7 @@ class PagureEventFilter(EventFilter):
def __repr__(self):
ret = '<PagureEventFilter'
ret += ' connection: %s' % self.connection_name
if self._types:
ret += ' types: %s' % ', '.join(self._types)
@ -149,6 +151,9 @@ class PagureEventFilter(EventFilter):
return ret
def matches(self, event, change):
if not super().matches(event, change):
return False
matches_type = False
for etype in self.types:
if etype.match(event.type):

View File

@ -23,10 +23,11 @@ class PagureTrigger(BaseTrigger):
name = 'pagure'
log = logging.getLogger("zuul.trigger.PagureTrigger")
def getEventFilters(self, trigger_config):
def getEventFilters(self, connection_name, trigger_config):
efilters = []
for trigger in to_list(trigger_config):
f = PagureEventFilter(
connection_name=connection_name,
trigger=self,
types=to_list(trigger['event']),
actions=to_list(trigger.get('action')),

View File

@ -18,8 +18,8 @@ from zuul.model import EventFilter, TriggerEvent
class TimerEventFilter(EventFilter):
def __init__(self, trigger, types=[], timespecs=[]):
EventFilter.__init__(self, trigger)
def __init__(self, connection_name, trigger, types=[], timespecs=[]):
EventFilter.__init__(self, connection_name, trigger)
self._types = types
self.types = [re.compile(x) for x in types]
@ -27,6 +27,7 @@ class TimerEventFilter(EventFilter):
def __repr__(self):
ret = '<TimerEventFilter'
ret += ' connection: %s' % self.connection_name
if self._types:
ret += ' types: %s' % ', '.join(self._types)

View File

@ -23,10 +23,11 @@ from zuul.driver.util import to_list
class TimerTrigger(BaseTrigger):
name = 'timer'
def getEventFilters(self, trigger_conf):
def getEventFilters(self, connection_name, trigger_conf):
efilters = []
for trigger in to_list(trigger_conf):
f = TimerEventFilter(trigger=self,
f = TimerEventFilter(connection_name=connection_name,
trigger=self,
types=['timer'],
timespecs=to_list(trigger['time']))

View File

@ -87,6 +87,7 @@ class ZuulDriver(Driver, TriggerInterface):
event = ZuulTriggerEvent()
event.type = PROJECT_CHANGE_MERGED
event.trigger_name = self.name
event.connection_name = "zuul"
event.project_hostname = change.project.canonical_hostname
event.project_name = change.project.name
event.change_number = change.number
@ -123,6 +124,7 @@ class ZuulDriver(Driver, TriggerInterface):
def _createParentChangeEnqueuedEvent(self, change, pipeline):
event = ZuulTriggerEvent()
event.type = PARENT_CHANGE_ENQUEUED
event.connection_name = "zuul"
event.trigger_name = self.name
event.pipeline_name = pipeline.name
event.project_hostname = change.project.canonical_hostname

View File

@ -18,8 +18,8 @@ from zuul.model import EventFilter, TriggerEvent
class ZuulEventFilter(EventFilter):
def __init__(self, trigger, types=[], pipelines=[]):
EventFilter.__init__(self, trigger)
def __init__(self, connection_name, trigger, types=[], pipelines=[]):
EventFilter.__init__(self, connection_name, trigger)
self._types = types
self._pipelines = pipelines
@ -28,6 +28,7 @@ class ZuulEventFilter(EventFilter):
def __repr__(self):
ret = '<ZuulEventFilter'
ret += ' connection: %s' % self.connection_name
if self._types:
ret += ' types: %s' % ', '.join(self._types)
@ -38,6 +39,9 @@ class ZuulEventFilter(EventFilter):
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:

View File

@ -29,10 +29,11 @@ class ZuulTrigger(BaseTrigger):
self._handle_parent_change_enqueued_events = False
self._handle_project_change_merged_events = False
def getEventFilters(self, trigger_conf):
def getEventFilters(self, connection_name, trigger_conf):
efilters = []
for trigger in to_list(trigger_conf):
f = ZuulEventFilter(
connection_name=connection_name,
trigger=self,
types=to_list(trigger['event']),
pipelines=to_list(trigger.get('pipeline')),

View File

@ -3932,6 +3932,7 @@ class TriggerEvent(AbstractEvent):
self.project_hostname = None
self.project_name = None
self.trigger_name = None
self.connection_name = None
# Representation of the user account that performed the event.
self.account = None
# patchset-created, comment-added, etc.
@ -3966,6 +3967,7 @@ class TriggerEvent(AbstractEvent):
"project_hostname": self.project_hostname,
"project_name": self.project_name,
"trigger_name": self.trigger_name,
"connection_name": self.connection_name,
"account": self.account,
"change_number": self.change_number,
"change_url": self.change_url,
@ -3995,6 +3997,7 @@ class TriggerEvent(AbstractEvent):
self.project_hostname = d["project_hostname"]
self.project_name = d["project_name"]
self.trigger_name = d["trigger_name"]
self.connection_name = d["connection_name"]
self.account = d["account"]
self.change_number = d["change_number"]
self.change_url = d["change_url"]
@ -4059,12 +4062,18 @@ class BaseFilter(ConfigObject):
class EventFilter(BaseFilter):
"""Allows a Pipeline to only respond to certain events."""
def __init__(self, trigger):
def __init__(self, connection_name, trigger):
super(EventFilter, self).__init__()
self.connection_name = connection_name
self.trigger = trigger
def matches(self, event, ref):
# TODO(jeblair): consider removing ref argument
# Event came from wrong connection
if self.connection_name != event.connection_name:
return False
return True

View File

@ -26,7 +26,7 @@ class BaseTrigger(object, metaclass=abc.ABCMeta):
self.config = config or {}
@abc.abstractmethod
def getEventFilters(self, trigger_conf):
def getEventFilters(self, connection_name, trigger_conf):
"""Return a list of EventFilter's for the scheduler to match against.
"""