Filter events on event connection

Currently if two triggers of the same connection type need to trigger on
different events it's not possible to do so since the events are never
filtered on which connection they came from.

For example with the following setup where gerrit-org-1 only wants to
trigger on changes to 'master' and gerrit-org-2 only wants to trigger on
changes to 'develop' they will instead both trigger on 'master' and
'develop'since the events are never filtered on which connection they
came from.

- pipeline:
    name: check
    trigger:
      gerrit-org-1:
        - event: patchset-created
          branch: 'master'
      gerrit-org-2:
        - event: patchset-created
          branch: 'develop'

Change-Id: Ia0476d71dee59c8b80db7630ac7a524bce87e6f9
This commit is contained in:
Albin Vass 2020-11-02 13:28:41 +01:00 committed by James E. Blair
parent 3ca33f0686
commit c81c2c6eec
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

@ -1366,15 +1366,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

@ -3799,6 +3799,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.
@ -3833,6 +3834,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,
@ -3862,6 +3864,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"]
@ -3926,12 +3929,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.
"""