summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bindep.txt2
-rw-r--r--doc/source/admin/drivers/github.rst6
-rw-r--r--releasenotes/notes/github-regex-status-26ddf3e3c91d182f.yaml7
-rw-r--r--requirements.txt1
-rw-r--r--tests/fixtures/layouts/requirements-github.yaml49
-rw-r--r--tests/unit/test_github_requirements.py30
-rw-r--r--zuul/driver/github/githubmodel.py23
7 files changed, 109 insertions, 9 deletions
diff --git a/bindep.txt b/bindep.txt
index 77b4a483f..7f05d2776 100644
--- a/bindep.txt
+++ b/bindep.txt
@@ -18,3 +18,5 @@ python3-dev [platform:dpkg]
python3-devel [platform:rpm]
bubblewrap [platform:rpm]
redhat-rpm-config [platform:rpm]
+libre2-dev [platform:dpkg]
+re2-devel [platform:rpm]
diff --git a/doc/source/admin/drivers/github.rst b/doc/source/admin/drivers/github.rst
index a27751f4d..9ed8b0ddc 100644
--- a/doc/source/admin/drivers/github.rst
+++ b/doc/source/admin/drivers/github.rst
@@ -219,7 +219,8 @@ the following options.
.. value:: status
- Status set on commit.
+ Status set on commit. The syntax is ``user:status:value``.
+ This also can be a regular expression.
A :value:`pipeline.trigger.<github
source>.event.pull_request_review` event will have associated
@@ -420,7 +421,8 @@ enqueued into the pipeline.
.. attr:: status
A string value that corresponds with the status of the pull
- request. The syntax is ``user:status:value``.
+ request. The syntax is ``user:status:value``. This can also
+ be a regular expression.
.. attr:: label
diff --git a/releasenotes/notes/github-regex-status-26ddf3e3c91d182f.yaml b/releasenotes/notes/github-regex-status-26ddf3e3c91d182f.yaml
new file mode 100644
index 000000000..bfdfe7e07
--- /dev/null
+++ b/releasenotes/notes/github-regex-status-26ddf3e3c91d182f.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ The GitHub trigger status filter
+ :value:`pipeline.trigger.<github source>.action.status` and pipeline
+ requirements :attr:`pipeline.require.<github source>.status` now support
+ regular expression matching.
diff --git a/requirements.txt b/requirements.txt
index f32053348..1c1f0c772 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -24,3 +24,4 @@ iso8601
aiohttp<3.0.0
uvloop;python_version>='3.5'
psutil
+fb-re2>=1.0.6
diff --git a/tests/fixtures/layouts/requirements-github.yaml b/tests/fixtures/layouts/requirements-github.yaml
index 04a083958..30ed44476 100644
--- a/tests/fixtures/layouts/requirements-github.yaml
+++ b/tests/fixtures/layouts/requirements-github.yaml
@@ -14,6 +14,21 @@
comment: true
- pipeline:
+ name: pipeline_regex
+ manager: independent
+ require:
+ github:
+ status: zuul:.*:success
+ trigger:
+ github:
+ - event: pull_request
+ action: comment
+ comment: test regex
+ success:
+ github:
+ comment: true
+
+- pipeline:
name: reject_status
manager: independent
reject:
@@ -29,6 +44,21 @@
comment: true
- pipeline:
+ name: reject_status_regex
+ manager: independent
+ reject:
+ github:
+ status: zuul:.*/check:error
+ trigger:
+ github:
+ - event: pull_request
+ action: comment
+ comment: test regex
+ success:
+ github:
+ comment: true
+
+- pipeline:
name: trigger_status
manager: independent
trigger:
@@ -37,6 +67,10 @@
action: comment
comment: trigger me
require-status: zuul:tenant-one/check:success
+ - event: pull_request
+ action: comment
+ comment: trigger regex
+ require-status: zuul:.*:success
success:
github:
comment: true
@@ -48,7 +82,14 @@
github:
- event: pull_request
action: status
- status: zuul:tenant-one/check:success
+ status:
+ # first line is to check if a list works here
+ - dummy:tenant-one/check:success
+ - zuul:tenant-one/check:success
+ - event: pull_request
+ action: status
+ status:
+ - other-ci:.+:success
success:
github:
status: success
@@ -314,6 +355,9 @@
pipeline:
jobs:
- project1-pipeline
+ pipeline_regex:
+ jobs:
+ - project1-pipeline
trigger_status:
jobs:
- project1-pipeline
@@ -383,6 +427,9 @@
reject_status:
jobs:
- project12-status
+ reject_status_regex:
+ jobs:
+ - project12-status
- project:
name: org/project13
diff --git a/tests/unit/test_github_requirements.py b/tests/unit/test_github_requirements.py
index e346b3308..8a0ca0b30 100644
--- a/tests/unit/test_github_requirements.py
+++ b/tests/unit/test_github_requirements.py
@@ -49,6 +49,12 @@ class TestGithubRequirements(ZuulTestCase):
self.assertEqual(len(self.history), 1)
self.assertEqual(self.history[0].name, 'project1-pipeline')
+ # Trigger regex matched status
+ self.fake_github.emitEvent(A.getCommentAddedEvent('test regex'))
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 2)
+ self.assertEqual(self.history[1].name, 'project1-pipeline')
+
@simple_layout('layouts/requirements-github.yaml', driver='github')
def test_trigger_require_status(self):
"Test trigger requirement: status"
@@ -76,6 +82,11 @@ class TestGithubRequirements(ZuulTestCase):
self.assertEqual(len(self.history), 1)
self.assertEqual(self.history[0].name, 'project1-pipeline')
+ self.fake_github.emitEvent(A.getCommentAddedEvent('trigger regex'))
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 2)
+ self.assertEqual(self.history[1].name, 'project1-pipeline')
+
@simple_layout('layouts/requirements-github.yaml', driver='github')
def test_trigger_on_status(self):
"Test trigger on: status"
@@ -118,6 +129,13 @@ class TestGithubRequirements(ZuulTestCase):
self.waitUntilSettled()
self.assertEqual(len(self.history), 1)
+ # A success status with a regex match goes in
+ self.fake_github.emitEvent(A.getCommitStatusEvent('cooltest',
+ user='other-ci'))
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 2)
+ self.assertEqual(self.history[1].name, 'project2-trigger')
+
@simple_layout('layouts/requirements-github.yaml', driver='github')
def test_pipeline_require_review_username(self):
"Test pipeline requirement: review username"
@@ -521,6 +539,12 @@ class TestGithubRequirements(ZuulTestCase):
# Status should cause it to be rejected
self.assertEqual(len(self.history), 0)
+ # Test that also the regex matched pipeline doesn't trigger
+ self.fake_github.emitEvent(A.getCommentAddedEvent('test regex'))
+ self.waitUntilSettled()
+ # Status should cause it to be rejected
+ self.assertEqual(len(self.history), 0)
+
self.fake_github.setCommitStatus(project, A.head_sha, 'success',
context='tenant-one/check')
# Now that status is not error, it should be enqueued
@@ -528,3 +552,9 @@ class TestGithubRequirements(ZuulTestCase):
self.waitUntilSettled()
self.assertEqual(len(self.history), 1)
self.assertEqual(self.history[0].name, 'project12-status')
+
+ # Test that also the regex matched pipeline triggers now
+ self.fake_github.emitEvent(A.getCommentAddedEvent('test regex'))
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 2)
+ self.assertEqual(self.history[1].name, 'project12-status')
diff --git a/zuul/driver/github/githubmodel.py b/zuul/driver/github/githubmodel.py
index 1a7ba20a2..5a2b18bb3 100644
--- a/zuul/driver/github/githubmodel.py
+++ b/zuul/driver/github/githubmodel.py
@@ -16,6 +16,7 @@
import copy
import re
+import re2
import time
from zuul.model import Change, TriggerEvent, EventFilter, RefFilter
@@ -171,16 +172,20 @@ class GithubCommonFilter(object):
# statuses and the filter statuses are a null intersection, there
# are no matches and we return false
if self.required_statuses:
- if set(change.status).isdisjoint(set(self.required_statuses)):
- return False
+ for required_status in self.required_statuses:
+ for status in change.status:
+ if re2.fullmatch(required_status, status):
+ return True
+ return False
return True
def matchesNoRejectStatuses(self, change):
# statuses are ANDed
# If any of the rejected statusses are present, we return false
for rstatus in self.reject_statuses:
- if rstatus in change.status:
- return False
+ for status in change.status:
+ if re2.fullmatch(rstatus, status):
+ return False
return True
@@ -299,8 +304,14 @@ class GithubEventFilter(EventFilter, GithubCommonFilter):
return False
# statuses are ORed
- if self.statuses and event.status not in self.statuses:
- return False
+ 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 False
if not self.matchesStatuses(change):
return False