Merge "Add GitHub pipeline trigger requirements"
This commit is contained in:
commit
8dcc69fbf0
|
@ -250,7 +250,8 @@ be able to invoke the ``gerrit stream-events`` command over SSH.
|
||||||
|
|
||||||
This takes a list of approvals in the same format as
|
This takes a list of approvals in the same format as
|
||||||
:attr:`pipeline.trigger.<gerrit source>.require-approval` but
|
:attr:`pipeline.trigger.<gerrit source>.require-approval` but
|
||||||
will fail to enter the pipeline if there is a matching approval.
|
the item will fail to enter the pipeline if there is a matching
|
||||||
|
approval.
|
||||||
|
|
||||||
Reporter Configuration
|
Reporter Configuration
|
||||||
----------------------
|
----------------------
|
||||||
|
|
|
@ -339,7 +339,7 @@ the following options.
|
||||||
format of ``user:context:status``. For example,
|
format of ``user:context:status``. For example,
|
||||||
``zuul_github_ci_bot:check_pipeline:success``.
|
``zuul_github_ci_bot:check_pipeline:success``.
|
||||||
|
|
||||||
.. attr: check
|
.. attr:: check
|
||||||
|
|
||||||
This is only used for ``check_run`` events. It works similar to
|
This is only used for ``check_run`` events. It works similar to
|
||||||
the ``status`` attribute and accepts a list of strings each of
|
the ``status`` attribute and accepts a list of strings each of
|
||||||
|
@ -363,6 +363,38 @@ the following options.
|
||||||
always sends full ref name, eg. ``refs/tags/bar`` and this
|
always sends full ref name, eg. ``refs/tags/bar`` and this
|
||||||
string is matched against the regular expression.
|
string is matched against the regular expression.
|
||||||
|
|
||||||
|
.. attr:: require-status
|
||||||
|
|
||||||
|
.. warning:: This is deprecated and will be removed in a future
|
||||||
|
version. Use :attr:`pipeline.trigger.<github
|
||||||
|
source>.require` instead.
|
||||||
|
|
||||||
|
This may be used for any event. It requires that a certain kind
|
||||||
|
of status be present for the PR (the status could be added by
|
||||||
|
the event in question). It follows the same syntax as
|
||||||
|
:attr:`pipeline.require.<github source>.status`. For each
|
||||||
|
specified criteria there must exist a matching status.
|
||||||
|
|
||||||
|
This is ignored if the :attr:`pipeline.trigger.<github
|
||||||
|
source>.require` attribute is present.
|
||||||
|
|
||||||
|
.. attr:: require
|
||||||
|
|
||||||
|
This may be used for any event. It describes conditions that
|
||||||
|
must be met by the PR 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:`github_requirements`.
|
||||||
|
|
||||||
|
.. attr:: reject
|
||||||
|
|
||||||
|
This may be used for any event and is the mirror of
|
||||||
|
:attr:`pipeline.trigger.<github source>.require`. It describes
|
||||||
|
conditions that when met by the PR cause the trigger event not
|
||||||
|
to match. Those conditions may be satisfied by the event in
|
||||||
|
question. It follows the same syntax as
|
||||||
|
:ref:`github_requirements`.
|
||||||
|
|
||||||
|
|
||||||
Reporter Configuration
|
Reporter Configuration
|
||||||
----------------------
|
----------------------
|
||||||
Zuul reports back to GitHub via GitHub API. Available reports include a PR
|
Zuul reports back to GitHub via GitHub API. Available reports include a PR
|
||||||
|
@ -462,6 +494,8 @@ itself. Status name, description, and context is taken from the pipeline.
|
||||||
|
|
||||||
.. _Github App: https://developer.github.com/apps/
|
.. _Github App: https://developer.github.com/apps/
|
||||||
|
|
||||||
|
.. _github_requirements:
|
||||||
|
|
||||||
Requirements Configuration
|
Requirements Configuration
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
GitHub 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.
|
||||||
|
deprecations:
|
||||||
|
- |
|
||||||
|
The `require-status` GitHub trigger attribute is deprecated.
|
||||||
|
Use :attr:`pipeline.trigger.<github source>.require` instead.
|
|
@ -0,0 +1,112 @@
|
||||||
|
- pipeline:
|
||||||
|
name: require-status
|
||||||
|
manager: independent
|
||||||
|
trigger:
|
||||||
|
github:
|
||||||
|
- event: pull_request
|
||||||
|
action: comment
|
||||||
|
comment: test require-status
|
||||||
|
require:
|
||||||
|
status:
|
||||||
|
- zuul:tenant-one/check:success
|
||||||
|
success:
|
||||||
|
github:
|
||||||
|
comment: true
|
||||||
|
|
||||||
|
- pipeline:
|
||||||
|
name: reject-status
|
||||||
|
manager: independent
|
||||||
|
trigger:
|
||||||
|
github:
|
||||||
|
- event: pull_request
|
||||||
|
action: comment
|
||||||
|
comment: test reject-status
|
||||||
|
reject:
|
||||||
|
status:
|
||||||
|
- zuul:tenant-one/check:failure
|
||||||
|
success:
|
||||||
|
github:
|
||||||
|
comment: true
|
||||||
|
|
||||||
|
- pipeline:
|
||||||
|
name: require-review
|
||||||
|
manager: independent
|
||||||
|
trigger:
|
||||||
|
github:
|
||||||
|
- event: pull_request
|
||||||
|
action: comment
|
||||||
|
comment: test require-review
|
||||||
|
require:
|
||||||
|
review:
|
||||||
|
- type: approved
|
||||||
|
permission: write
|
||||||
|
success:
|
||||||
|
github:
|
||||||
|
comment: true
|
||||||
|
|
||||||
|
- pipeline:
|
||||||
|
name: reject-review
|
||||||
|
manager: independent
|
||||||
|
trigger:
|
||||||
|
github:
|
||||||
|
- event: pull_request
|
||||||
|
action: comment
|
||||||
|
comment: test reject-review
|
||||||
|
reject:
|
||||||
|
review:
|
||||||
|
- type: changes_requested
|
||||||
|
permission: write
|
||||||
|
success:
|
||||||
|
github:
|
||||||
|
comment: true
|
||||||
|
|
||||||
|
- pipeline:
|
||||||
|
name: require-label
|
||||||
|
manager: independent
|
||||||
|
trigger:
|
||||||
|
github:
|
||||||
|
- event: pull_request
|
||||||
|
action: comment
|
||||||
|
comment: test require-label
|
||||||
|
require:
|
||||||
|
label:
|
||||||
|
- approved
|
||||||
|
success:
|
||||||
|
github:
|
||||||
|
comment: true
|
||||||
|
|
||||||
|
- pipeline:
|
||||||
|
name: reject-label
|
||||||
|
manager: independent
|
||||||
|
trigger:
|
||||||
|
github:
|
||||||
|
- event: pull_request
|
||||||
|
action: comment
|
||||||
|
comment: test reject-label
|
||||||
|
reject:
|
||||||
|
label:
|
||||||
|
- rejected
|
||||||
|
success:
|
||||||
|
github:
|
||||||
|
comment: true
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: base
|
||||||
|
parent: null
|
||||||
|
run: playbooks/base.yaml
|
||||||
|
|
||||||
|
- job: {name: require-status}
|
||||||
|
- job: {name: reject-status}
|
||||||
|
- job: {name: require-review}
|
||||||
|
- job: {name: reject-review}
|
||||||
|
- job: {name: require-label}
|
||||||
|
- job: {name: reject-label}
|
||||||
|
|
||||||
|
- project:
|
||||||
|
name: org/project
|
||||||
|
require-status: {jobs: [require-status]}
|
||||||
|
reject-status: {jobs: [reject-status]}
|
||||||
|
require-review: {jobs: [require-review]}
|
||||||
|
reject-review: {jobs: [reject-review]}
|
||||||
|
require-label: {jobs: [require-label]}
|
||||||
|
reject-label: {jobs: [reject-label]}
|
|
@ -678,3 +678,181 @@ class TestGithubAppRequirements(ZuulGithubAppTestCase):
|
||||||
self.fake_github.emitEvent(comment)
|
self.fake_github.emitEvent(comment)
|
||||||
self.waitUntilSettled()
|
self.waitUntilSettled()
|
||||||
self.assertEqual(len(self.history), 1)
|
self.assertEqual(len(self.history), 1)
|
||||||
|
|
||||||
|
|
||||||
|
class TestGithubTriggerRequirements(ZuulTestCase):
|
||||||
|
"""Test pipeline and trigger requirements"""
|
||||||
|
config_file = 'zuul-github-driver.conf'
|
||||||
|
scheduler_count = 1
|
||||||
|
|
||||||
|
@simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
|
||||||
|
def test_require_status(self):
|
||||||
|
# Test trigger require-status
|
||||||
|
jobname = 'require-status'
|
||||||
|
project = 'org/project'
|
||||||
|
A = self.fake_github.openFakePullRequest(project, 'master', 'A')
|
||||||
|
# A comment event that we will keep submitting to trigger
|
||||||
|
comment = A.getCommentAddedEvent(f'test {jobname}')
|
||||||
|
|
||||||
|
# No status from zuul so should not be enqueued
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 0)
|
||||||
|
|
||||||
|
# An error status should not cause it to be enqueued
|
||||||
|
self.fake_github.setCommitStatus(project, A.head_sha, 'error',
|
||||||
|
context='tenant-one/check')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 0)
|
||||||
|
|
||||||
|
# A success status goes in
|
||||||
|
self.fake_github.setCommitStatus(project, A.head_sha, 'success',
|
||||||
|
context='tenant-one/check')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 1)
|
||||||
|
self.assertEqual(self.history[0].name, jobname)
|
||||||
|
|
||||||
|
@simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
|
||||||
|
def test_reject_status(self):
|
||||||
|
# Test trigger reject-status
|
||||||
|
jobname = 'reject-status'
|
||||||
|
project = 'org/project'
|
||||||
|
A = self.fake_github.openFakePullRequest(project, 'master', 'A')
|
||||||
|
# A comment event that we will keep submitting to trigger
|
||||||
|
comment = A.getCommentAddedEvent(f'test {jobname}')
|
||||||
|
|
||||||
|
# No status from zuul so should be enqueued
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 1)
|
||||||
|
self.assertEqual(self.history[0].name, jobname)
|
||||||
|
|
||||||
|
# A failure status should not cause it to be enqueued
|
||||||
|
self.fake_github.setCommitStatus(project, A.head_sha, 'failure',
|
||||||
|
context='tenant-one/check')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 1)
|
||||||
|
|
||||||
|
# A success status goes in
|
||||||
|
self.fake_github.setCommitStatus(project, A.head_sha, 'success',
|
||||||
|
context='tenant-one/check')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 2)
|
||||||
|
self.assertEqual(self.history[1].name, jobname)
|
||||||
|
|
||||||
|
@simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
|
||||||
|
def test_require_review(self):
|
||||||
|
# Test trigger require-review
|
||||||
|
jobname = 'require-review'
|
||||||
|
project = 'org/project'
|
||||||
|
A = self.fake_github.openFakePullRequest(project, 'master', 'A')
|
||||||
|
A.writers.extend(('maintainer',))
|
||||||
|
# A comment event that we will keep submitting to trigger
|
||||||
|
comment = A.getCommentAddedEvent(f'test {jobname}')
|
||||||
|
|
||||||
|
# No review so should not be enqueued
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 0)
|
||||||
|
|
||||||
|
# An changes requested review should not cause it to be enqueued
|
||||||
|
A.addReview('maintainer', 'CHANGES_REQUESTED')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 0)
|
||||||
|
|
||||||
|
# A positive review goes in
|
||||||
|
A.addReview('maintainer', 'APPROVED')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 1)
|
||||||
|
self.assertEqual(self.history[0].name, jobname)
|
||||||
|
|
||||||
|
@simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
|
||||||
|
def test_reject_review(self):
|
||||||
|
# Test trigger reject-review
|
||||||
|
jobname = 'reject-review'
|
||||||
|
project = 'org/project'
|
||||||
|
A = self.fake_github.openFakePullRequest(project, 'master', 'A')
|
||||||
|
A.writers.extend(('maintainer',))
|
||||||
|
# A comment event that we will keep submitting to trigger
|
||||||
|
comment = A.getCommentAddedEvent(f'test {jobname}')
|
||||||
|
|
||||||
|
# No review so should be enqueued
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 1)
|
||||||
|
self.assertEqual(self.history[0].name, jobname)
|
||||||
|
|
||||||
|
# An changes requested review should not cause it to be enqueued
|
||||||
|
A.addReview('maintainer', 'CHANGES_REQUESTED')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 1)
|
||||||
|
|
||||||
|
# A positive review goes in
|
||||||
|
A.addReview('maintainer', 'APPROVED')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 2)
|
||||||
|
self.assertEqual(self.history[1].name, jobname)
|
||||||
|
|
||||||
|
@simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
|
||||||
|
def test_require_label(self):
|
||||||
|
# Test trigger require-label
|
||||||
|
jobname = 'require-label'
|
||||||
|
project = 'org/project'
|
||||||
|
A = self.fake_github.openFakePullRequest(project, 'master', 'A')
|
||||||
|
# A comment event that we will keep submitting to trigger
|
||||||
|
comment = A.getCommentAddedEvent(f'test {jobname}')
|
||||||
|
|
||||||
|
# No label so should not be enqueued
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 0)
|
||||||
|
|
||||||
|
# A random should not cause it to be enqueued
|
||||||
|
A.addLabel('foobar')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 0)
|
||||||
|
|
||||||
|
# An approved label goes in
|
||||||
|
A.addLabel('approved')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 1)
|
||||||
|
self.assertEqual(self.history[0].name, jobname)
|
||||||
|
|
||||||
|
@simple_layout('layouts/github-trigger-requirements.yaml', driver='github')
|
||||||
|
def test_reject_label(self):
|
||||||
|
# Test trigger reject-label
|
||||||
|
jobname = 'reject-label'
|
||||||
|
project = 'org/project'
|
||||||
|
A = self.fake_github.openFakePullRequest(project, 'master', 'A')
|
||||||
|
# A comment event that we will keep submitting to trigger
|
||||||
|
comment = A.getCommentAddedEvent(f'test {jobname}')
|
||||||
|
|
||||||
|
# No label so should be enqueued
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 1)
|
||||||
|
self.assertEqual(self.history[0].name, jobname)
|
||||||
|
|
||||||
|
# A rejected label should not cause it to be enqueued
|
||||||
|
A.addLabel('rejected')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 1)
|
||||||
|
|
||||||
|
# Any other label, it goes in
|
||||||
|
A.removeLabel('rejected')
|
||||||
|
A.addLabel('okay')
|
||||||
|
self.fake_github.emitEvent(comment)
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertEqual(len(self.history), 2)
|
||||||
|
self.assertEqual(self.history[1].name, jobname)
|
||||||
|
|
|
@ -21,7 +21,7 @@ import time
|
||||||
|
|
||||||
from zuul.model import Change, TriggerEvent, EventFilter, RefFilter
|
from zuul.model import Change, TriggerEvent, EventFilter, RefFilter
|
||||||
from zuul.model import FalseWithReason
|
from zuul.model import FalseWithReason
|
||||||
from zuul.driver.util import time_to_seconds
|
from zuul.driver.util import time_to_seconds, to_list
|
||||||
|
|
||||||
|
|
||||||
EMPTY_GIT_REF = '0' * 40 # git sha of all zeros, used during creates/deletes
|
EMPTY_GIT_REF = '0' * 40 # git sha of all zeros, used during creates/deletes
|
||||||
|
@ -170,15 +170,277 @@ class GithubTriggerEvent(TriggerEvent):
|
||||||
return ' '.join(r)
|
return ' '.join(r)
|
||||||
|
|
||||||
|
|
||||||
class GithubCommonFilter(object):
|
class GithubEventFilter(EventFilter):
|
||||||
def __init__(self, required_reviews=[], required_statuses=[],
|
def __init__(self, connection_name, trigger, types=[],
|
||||||
reject_reviews=[], reject_statuses=[]):
|
branches=[], refs=[], comments=[], actions=[],
|
||||||
self._required_reviews = copy.deepcopy(required_reviews)
|
labels=[], unlabels=[], states=[], statuses=[],
|
||||||
|
required_statuses=[], check_runs=[],
|
||||||
|
ignore_deletes=True,
|
||||||
|
require=None, reject=None):
|
||||||
|
|
||||||
|
EventFilter.__init__(self, connection_name, trigger)
|
||||||
|
|
||||||
|
# TODO: Backwards compat, remove after 9.x:
|
||||||
|
if required_statuses and require is None:
|
||||||
|
require = {'status': required_statuses}
|
||||||
|
|
||||||
|
if require:
|
||||||
|
self.require_filter = GithubRefFilter.requiresFromConfig(
|
||||||
|
connection_name, require)
|
||||||
|
else:
|
||||||
|
self.require_filter = None
|
||||||
|
|
||||||
|
if reject:
|
||||||
|
self.reject_filter = GithubRefFilter.rejectFromConfig(
|
||||||
|
connection_name, reject)
|
||||||
|
else:
|
||||||
|
self.reject_filter = None
|
||||||
|
|
||||||
|
self._types = types
|
||||||
|
self._branches = branches
|
||||||
|
self._refs = refs
|
||||||
|
self._comments = comments
|
||||||
|
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.actions = actions
|
||||||
|
self.labels = labels
|
||||||
|
self.unlabels = unlabels
|
||||||
|
self.states = states
|
||||||
|
self.statuses = statuses
|
||||||
|
self.check_runs = check_runs
|
||||||
|
self.ignore_deletes = ignore_deletes
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
ret = '<GithubEventFilter'
|
||||||
|
ret += ' connection: %s' % self.connection_name
|
||||||
|
if self._types:
|
||||||
|
ret += ' types: %s' % ', '.join(self._types)
|
||||||
|
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._comments:
|
||||||
|
ret += ' comments: %s' % ', '.join(self._comments)
|
||||||
|
if self.actions:
|
||||||
|
ret += ' actions: %s' % ', '.join(self.actions)
|
||||||
|
if self.check_runs:
|
||||||
|
ret += ' check_runs: %s' % ','.join(self.check_runs)
|
||||||
|
if self.labels:
|
||||||
|
ret += ' labels: %s' % ', '.join(self.labels)
|
||||||
|
if self.unlabels:
|
||||||
|
ret += ' unlabels: %s' % ', '.join(self.unlabels)
|
||||||
|
if self.states:
|
||||||
|
ret += ' states: %s' % ', '.join(self.states)
|
||||||
|
if self.statuses:
|
||||||
|
ret += ' statuses: %s' % ', '.join(self.statuses)
|
||||||
|
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 doesn't match %s" % (
|
||||||
|
self.types, event.type))
|
||||||
|
|
||||||
|
# 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 doesn't 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 doesn't 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")
|
||||||
|
|
||||||
|
# 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 self.comments and not matches_comment_re:
|
||||||
|
return FalseWithReason("Comments %s doesn't match %s" % (
|
||||||
|
self.comments, event.comment))
|
||||||
|
|
||||||
|
# actions are ORed
|
||||||
|
matches_action = False
|
||||||
|
for action in self.actions:
|
||||||
|
if (event.action == action):
|
||||||
|
matches_action = True
|
||||||
|
if self.actions and not matches_action:
|
||||||
|
return FalseWithReason("Actions %s doesn't match %s" % (
|
||||||
|
self.actions, event.action))
|
||||||
|
|
||||||
|
# check_runs are ORed
|
||||||
|
if self.check_runs:
|
||||||
|
check_run_found = False
|
||||||
|
for check_run in self.check_runs:
|
||||||
|
if re2.fullmatch(check_run, event.check_run):
|
||||||
|
check_run_found = True
|
||||||
|
break
|
||||||
|
if not check_run_found:
|
||||||
|
return FalseWithReason("Check_runs %s doesn't 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" % (
|
||||||
|
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" % (
|
||||||
|
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" % (
|
||||||
|
self.states, event.state))
|
||||||
|
|
||||||
|
# statuses are ORed
|
||||||
|
if self.statuses:
|
||||||
|
status_found = False
|
||||||
|
for status in self.statuses:
|
||||||
|
if re2.fullmatch(status, event.status):
|
||||||
|
status_found = True
|
||||||
|
break
|
||||||
|
if not status_found:
|
||||||
|
return FalseWithReason("Statuses %s doesn't match %s" % (
|
||||||
|
self.statuses, event.status))
|
||||||
|
|
||||||
|
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 GithubRefFilter(RefFilter):
|
||||||
|
def __init__(self, connection_name, statuses=[],
|
||||||
|
reviews=[], reject_reviews=[], open=None,
|
||||||
|
merged=None, current_patchset=None, draft=None,
|
||||||
|
reject_open=None, reject_merged=None,
|
||||||
|
reject_current_patchset=None, reject_draft=None,
|
||||||
|
labels=[], reject_labels=[], reject_statuses=[]):
|
||||||
|
RefFilter.__init__(self, connection_name)
|
||||||
|
|
||||||
|
self._required_reviews = copy.deepcopy(reviews)
|
||||||
self._reject_reviews = copy.deepcopy(reject_reviews)
|
self._reject_reviews = copy.deepcopy(reject_reviews)
|
||||||
self.required_reviews = self._tidy_reviews(self._required_reviews)
|
self.required_reviews = self._tidy_reviews(self._required_reviews)
|
||||||
self.reject_reviews = self._tidy_reviews(self._reject_reviews)
|
self.reject_reviews = self._tidy_reviews(self._reject_reviews)
|
||||||
self.required_statuses = required_statuses
|
self.required_statuses = statuses
|
||||||
self.reject_statuses = reject_statuses
|
self.reject_statuses = reject_statuses
|
||||||
|
self.required_labels = labels
|
||||||
|
self.reject_labels = reject_labels
|
||||||
|
|
||||||
|
if reject_open is not None:
|
||||||
|
self.open = not reject_open
|
||||||
|
else:
|
||||||
|
self.open = open
|
||||||
|
if reject_merged is not None:
|
||||||
|
self.merged = not reject_merged
|
||||||
|
else:
|
||||||
|
self.merged = merged
|
||||||
|
if reject_current_patchset is not None:
|
||||||
|
self.current_patchset = not reject_current_patchset
|
||||||
|
else:
|
||||||
|
self.current_patchset = current_patchset
|
||||||
|
if reject_draft is not None:
|
||||||
|
self.draft = not reject_draft
|
||||||
|
else:
|
||||||
|
self.draft = draft
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def requiresFromConfig(cls, connection_name, config):
|
||||||
|
return cls(
|
||||||
|
connection_name=connection_name,
|
||||||
|
statuses=to_list(config.get('status')),
|
||||||
|
reviews=to_list(config.get('review')),
|
||||||
|
labels=to_list(config.get('label')),
|
||||||
|
open=config.get('open'),
|
||||||
|
merged=config.get('merged'),
|
||||||
|
current_patchset=config.get('current-patchset'),
|
||||||
|
draft=config.get('draft'),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def rejectFromConfig(cls, connection_name, config):
|
||||||
|
return cls(
|
||||||
|
connection_name=connection_name,
|
||||||
|
reject_statuses=to_list(config.get('status')),
|
||||||
|
reject_reviews=to_list(config.get('review')),
|
||||||
|
reject_labels=to_list(config.get('label')),
|
||||||
|
reject_open=config.get('open'),
|
||||||
|
reject_merged=config.get('merged'),
|
||||||
|
reject_current_patchset=config.get('current-patchset'),
|
||||||
|
reject_draft=config.get('draft'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
ret = '<GithubRefFilter'
|
||||||
|
|
||||||
|
ret += ' connection_name: %s' % self.connection_name
|
||||||
|
if self.required_statuses:
|
||||||
|
ret += ' status: %s' % str(self.required_statuses)
|
||||||
|
if self.reject_statuses:
|
||||||
|
ret += ' reject-status: %s' % str(self.reject_statuses)
|
||||||
|
if self.required_reviews:
|
||||||
|
ret += (' reviews: %s' %
|
||||||
|
str(self.required_reviews))
|
||||||
|
if self.reject_reviews:
|
||||||
|
ret += (' reject-reviews: %s' %
|
||||||
|
str(self.reject_reviews))
|
||||||
|
if self.required_labels:
|
||||||
|
ret += ' labels: %s' % str(self.required_labels)
|
||||||
|
if self.reject_labels:
|
||||||
|
ret += ' reject-labels: %s' % str(self.reject_labels)
|
||||||
|
if self.open is not None:
|
||||||
|
ret += ' open: %s' % self.open
|
||||||
|
if self.merged is not None:
|
||||||
|
ret += ' merged: %s' % self.merged
|
||||||
|
if self.current_patchset is not None:
|
||||||
|
ret += ' current-patchset: %s' % self.current_patchset
|
||||||
|
if self.draft is not None:
|
||||||
|
ret += ' draft: %s' % self.draft
|
||||||
|
|
||||||
|
ret += '>'
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
def _tidy_reviews(self, reviews):
|
def _tidy_reviews(self, reviews):
|
||||||
for r in reviews:
|
for r in reviews:
|
||||||
|
@ -303,229 +565,47 @@ class GithubCommonFilter(object):
|
||||||
self.reject_statuses, change.status))
|
self.reject_statuses, change.status))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def matchesLabels(self, change):
|
||||||
|
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")
|
||||||
|
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
|
||||||
|
return FalseWithReason(
|
||||||
|
"Required labels %s does not match %s" % (
|
||||||
|
self.required_labels, change.labels))
|
||||||
|
return (self.matchesRequiredLabels(change) and
|
||||||
|
self.matchesNoRejectLabels(change))
|
||||||
|
|
||||||
class GithubEventFilter(EventFilter, GithubCommonFilter):
|
def matchesRequiredLabels(self, change):
|
||||||
def __init__(self, connection_name, trigger, types=[], branches=[],
|
for label in self.required_labels:
|
||||||
refs=[], comments=[], actions=[], labels=[], unlabels=[],
|
if label not in change.labels:
|
||||||
states=[], statuses=[], required_statuses=[],
|
return FalseWithReason("Labels %s does not match %s" % (
|
||||||
check_runs=[], ignore_deletes=True):
|
self.required_labels, change.labels))
|
||||||
|
return True
|
||||||
|
|
||||||
EventFilter.__init__(self, connection_name, trigger)
|
def matchesNoRejectLabels(self, change):
|
||||||
|
for label in self.reject_labels:
|
||||||
GithubCommonFilter.__init__(self, required_statuses=required_statuses)
|
if label in change.labels:
|
||||||
|
return FalseWithReason("NoRejectLabels %s matches %s" % (
|
||||||
self._types = types
|
self.reject_labels, change.labels))
|
||||||
self._branches = branches
|
return True
|
||||||
self._refs = refs
|
|
||||||
self._comments = comments
|
|
||||||
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.actions = actions
|
|
||||||
self.labels = labels
|
|
||||||
self.unlabels = unlabels
|
|
||||||
self.states = states
|
|
||||||
self.statuses = statuses
|
|
||||||
self.required_statuses = required_statuses
|
|
||||||
self.check_runs = check_runs
|
|
||||||
self.ignore_deletes = ignore_deletes
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
ret = '<GithubEventFilter'
|
|
||||||
ret += ' connection: %s' % self.connection_name
|
|
||||||
if self._types:
|
|
||||||
ret += ' types: %s' % ', '.join(self._types)
|
|
||||||
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._comments:
|
|
||||||
ret += ' comments: %s' % ', '.join(self._comments)
|
|
||||||
if self.actions:
|
|
||||||
ret += ' actions: %s' % ', '.join(self.actions)
|
|
||||||
if self.check_runs:
|
|
||||||
ret += ' check_runs: %s' % ','.join(self.check_runs)
|
|
||||||
if self.labels:
|
|
||||||
ret += ' labels: %s' % ', '.join(self.labels)
|
|
||||||
if self.unlabels:
|
|
||||||
ret += ' unlabels: %s' % ', '.join(self.unlabels)
|
|
||||||
if self.states:
|
|
||||||
ret += ' states: %s' % ', '.join(self.states)
|
|
||||||
if self.statuses:
|
|
||||||
ret += ' statuses: %s' % ', '.join(self.statuses)
|
|
||||||
if self.required_statuses:
|
|
||||||
ret += ' required_statuses: %s' % ', '.join(self.required_statuses)
|
|
||||||
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 doesn't match %s" % (
|
|
||||||
self.types, event.type))
|
|
||||||
|
|
||||||
# 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 doesn't 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 doesn't 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")
|
|
||||||
|
|
||||||
# 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 self.comments and not matches_comment_re:
|
|
||||||
return FalseWithReason("Comments %s doesn't match %s" % (
|
|
||||||
self.comments, event.comment))
|
|
||||||
|
|
||||||
# actions are ORed
|
|
||||||
matches_action = False
|
|
||||||
for action in self.actions:
|
|
||||||
if (event.action == action):
|
|
||||||
matches_action = True
|
|
||||||
if self.actions and not matches_action:
|
|
||||||
return FalseWithReason("Actions %s doesn't match %s" % (
|
|
||||||
self.actions, event.action))
|
|
||||||
|
|
||||||
# check_runs are ORed
|
|
||||||
if self.check_runs:
|
|
||||||
check_run_found = False
|
|
||||||
for check_run in self.check_runs:
|
|
||||||
if re2.fullmatch(check_run, event.check_run):
|
|
||||||
check_run_found = True
|
|
||||||
break
|
|
||||||
if not check_run_found:
|
|
||||||
return FalseWithReason("Check_runs %s doesn't 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" % (
|
|
||||||
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" % (
|
|
||||||
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" % (
|
|
||||||
self.states, event.state))
|
|
||||||
|
|
||||||
# statuses are ORed
|
|
||||||
if self.statuses:
|
|
||||||
status_found = False
|
|
||||||
for status in self.statuses:
|
|
||||||
if re2.fullmatch(status, event.status):
|
|
||||||
status_found = True
|
|
||||||
break
|
|
||||||
if not status_found:
|
|
||||||
return FalseWithReason("Statuses %s doesn't match %s" % (
|
|
||||||
self.statuses, event.status))
|
|
||||||
|
|
||||||
return self.matchesStatuses(change)
|
|
||||||
|
|
||||||
|
|
||||||
class GithubRefFilter(RefFilter, GithubCommonFilter):
|
|
||||||
def __init__(self, connection_name, statuses=[],
|
|
||||||
required_reviews=[], reject_reviews=[], open=None,
|
|
||||||
merged=None, current_patchset=None, draft=None,
|
|
||||||
reject_open=None, reject_merged=None,
|
|
||||||
reject_current_patchset=None, reject_draft=None,
|
|
||||||
labels=[], reject_labels=[], reject_statuses=[]):
|
|
||||||
RefFilter.__init__(self, connection_name)
|
|
||||||
|
|
||||||
GithubCommonFilter.__init__(self, required_reviews=required_reviews,
|
|
||||||
reject_reviews=reject_reviews,
|
|
||||||
required_statuses=statuses,
|
|
||||||
reject_statuses=reject_statuses)
|
|
||||||
self.statuses = statuses
|
|
||||||
if reject_open is not None:
|
|
||||||
self.open = not reject_open
|
|
||||||
else:
|
|
||||||
self.open = open
|
|
||||||
if reject_merged is not None:
|
|
||||||
self.merged = not reject_merged
|
|
||||||
else:
|
|
||||||
self.merged = merged
|
|
||||||
if reject_current_patchset is not None:
|
|
||||||
self.current_patchset = not reject_current_patchset
|
|
||||||
else:
|
|
||||||
self.current_patchset = current_patchset
|
|
||||||
if reject_draft is not None:
|
|
||||||
self.draft = not reject_draft
|
|
||||||
else:
|
|
||||||
self.draft = draft
|
|
||||||
self.labels = labels
|
|
||||||
self.reject_labels = reject_labels
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
ret = '<GithubRefFilter'
|
|
||||||
|
|
||||||
ret += ' connection_name: %s' % self.connection_name
|
|
||||||
if self.statuses:
|
|
||||||
ret += ' statuses: %s' % ', '.join(self.statuses)
|
|
||||||
if self.reject_statuses:
|
|
||||||
ret += ' reject-statuses: %s' % ', '.join(self.reject_statuses)
|
|
||||||
if self.required_reviews:
|
|
||||||
ret += (' required-reviews: %s' %
|
|
||||||
str(self.required_reviews))
|
|
||||||
if self.reject_reviews:
|
|
||||||
ret += (' reject-reviews: %s' %
|
|
||||||
str(self.reject_reviews))
|
|
||||||
if self.open is not None:
|
|
||||||
ret += ' open: %s' % self.open
|
|
||||||
if self.merged is not None:
|
|
||||||
ret += ' merged: %s' % self.merged
|
|
||||||
if self.current_patchset is not None:
|
|
||||||
ret += ' current-patchset: %s' % self.current_patchset
|
|
||||||
if self.draft is not None:
|
|
||||||
ret += ' draft: %s' % self.draft
|
|
||||||
if self.labels:
|
|
||||||
ret += ' labels: %s' % self.labels
|
|
||||||
if self.reject_labels:
|
|
||||||
ret += ' reject-labels: %s' % self.reject_labels
|
|
||||||
|
|
||||||
ret += '>'
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def matches(self, change):
|
def matches(self, change):
|
||||||
statuses_result = self.matchesStatuses(change)
|
statuses_result = self.matchesStatuses(change)
|
||||||
if not statuses_result:
|
if not statuses_result:
|
||||||
return statuses_result
|
return statuses_result
|
||||||
|
|
||||||
|
reviews_result = self.matchesReviews(change)
|
||||||
|
if not reviews_result:
|
||||||
|
return reviews_result
|
||||||
|
|
||||||
|
labels_result = self.matchesLabels(change)
|
||||||
|
if not labels_result:
|
||||||
|
return labels_result
|
||||||
|
|
||||||
if self.open is not None:
|
if self.open is not None:
|
||||||
# if a "change" has no number, it's not a change, but a push
|
# if a "change" has no number, it's not a change, but a push
|
||||||
# and cannot possibly pass this test.
|
# and cannot possibly pass this test.
|
||||||
|
@ -566,21 +646,4 @@ class GithubRefFilter(RefFilter, GithubCommonFilter):
|
||||||
else:
|
else:
|
||||||
return FalseWithReason("Change is not a PR")
|
return FalseWithReason("Change is not a PR")
|
||||||
|
|
||||||
# required reviews are ANDed (reject reviews are ORed)
|
|
||||||
reviews_result = self.matchesReviews(change)
|
|
||||||
if not reviews_result:
|
|
||||||
return reviews_result
|
|
||||||
|
|
||||||
# required labels are ANDed
|
|
||||||
for label in self.labels:
|
|
||||||
if label not in change.labels:
|
|
||||||
return FalseWithReason("Labels %s does not match %s" % (
|
|
||||||
self.labels, change.labels))
|
|
||||||
|
|
||||||
# rejected reviews are OR'd
|
|
||||||
for label in self.reject_labels:
|
|
||||||
if label in change.labels:
|
|
||||||
return FalseWithReason("RejectLabels %s matches %s" % (
|
|
||||||
self.reject_labels, change.labels))
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -21,7 +21,7 @@ import voluptuous as v
|
||||||
from zuul.source import BaseSource
|
from zuul.source import BaseSource
|
||||||
from zuul.model import Project
|
from zuul.model import Project
|
||||||
from zuul.driver.github.githubmodel import GithubRefFilter
|
from zuul.driver.github.githubmodel import GithubRefFilter
|
||||||
from zuul.driver.util import scalar_or_list, to_list
|
from zuul.driver.util import scalar_or_list
|
||||||
from zuul.zk.change_cache import ChangeKey
|
from zuul.zk.change_cache import ChangeKey
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,29 +165,15 @@ class GithubSource(BaseSource):
|
||||||
return time.strptime(timestamp, '%Y-%m-%dT%H:%M:%SZ')
|
return time.strptime(timestamp, '%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
|
||||||
def getRequireFilters(self, config):
|
def getRequireFilters(self, config):
|
||||||
f = GithubRefFilter(
|
f = GithubRefFilter.requiresFromConfig(
|
||||||
connection_name=self.connection.connection_name,
|
self.connection.connection_name,
|
||||||
statuses=to_list(config.get('status')),
|
config)
|
||||||
required_reviews=to_list(config.get('review')),
|
|
||||||
open=config.get('open'),
|
|
||||||
merged=config.get('merged'),
|
|
||||||
current_patchset=config.get('current-patchset'),
|
|
||||||
draft=config.get('draft'),
|
|
||||||
labels=to_list(config.get('label')),
|
|
||||||
)
|
|
||||||
return [f]
|
return [f]
|
||||||
|
|
||||||
def getRejectFilters(self, config):
|
def getRejectFilters(self, config):
|
||||||
f = GithubRefFilter(
|
f = GithubRefFilter.rejectFromConfig(
|
||||||
connection_name=self.connection.connection_name,
|
self.connection.connection_name,
|
||||||
reject_reviews=to_list(config.get('review')),
|
config)
|
||||||
reject_labels=to_list(config.get('label')),
|
|
||||||
reject_statuses=to_list(config.get('status')),
|
|
||||||
reject_open=config.get('open'),
|
|
||||||
reject_merged=config.get('merged'),
|
|
||||||
reject_current_patchset=config.get('current-patchset'),
|
|
||||||
reject_draft=config.get('draft'),
|
|
||||||
)
|
|
||||||
return [f]
|
return [f]
|
||||||
|
|
||||||
def getRefForChange(self, change):
|
def getRefForChange(self, change):
|
||||||
|
|
|
@ -16,6 +16,7 @@ import logging
|
||||||
import voluptuous as v
|
import voluptuous as v
|
||||||
from zuul.trigger import BaseTrigger
|
from zuul.trigger import BaseTrigger
|
||||||
from zuul.driver.github.githubmodel import GithubEventFilter
|
from zuul.driver.github.githubmodel import GithubEventFilter
|
||||||
|
from zuul.driver.github import githubsource
|
||||||
from zuul.driver.util import scalar_or_list, to_list
|
from zuul.driver.util import scalar_or_list, to_list
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,7 +51,9 @@ class GithubTrigger(BaseTrigger):
|
||||||
unlabels=to_list(trigger.get('unlabel')),
|
unlabels=to_list(trigger.get('unlabel')),
|
||||||
states=to_list(trigger.get('state')),
|
states=to_list(trigger.get('state')),
|
||||||
statuses=to_list(trigger.get('status')),
|
statuses=to_list(trigger.get('status')),
|
||||||
required_statuses=to_list(trigger.get('require-status'))
|
required_statuses=to_list(trigger.get('require-status')),
|
||||||
|
require=trigger.get('require'),
|
||||||
|
reject=trigger.get('reject'),
|
||||||
)
|
)
|
||||||
efilters.append(f)
|
efilters.append(f)
|
||||||
|
|
||||||
|
@ -75,6 +78,8 @@ def getSchema():
|
||||||
'unlabel': scalar_or_list(str),
|
'unlabel': scalar_or_list(str),
|
||||||
'state': scalar_or_list(str),
|
'state': scalar_or_list(str),
|
||||||
'require-status': scalar_or_list(str),
|
'require-status': scalar_or_list(str),
|
||||||
|
'require': githubsource.getRequireSchema(),
|
||||||
|
'reject': githubsource.getRejectSchema(),
|
||||||
'status': scalar_or_list(str),
|
'status': scalar_or_list(str),
|
||||||
'check': scalar_or_list(str),
|
'check': scalar_or_list(str),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue