diff options
-rw-r--r-- | bindep.txt | 2 | ||||
-rw-r--r-- | doc/source/admin/drivers/github.rst | 6 | ||||
-rw-r--r-- | releasenotes/notes/github-regex-status-26ddf3e3c91d182f.yaml | 7 | ||||
-rw-r--r-- | requirements.txt | 1 | ||||
-rw-r--r-- | tests/fixtures/layouts/requirements-github.yaml | 49 | ||||
-rw-r--r-- | tests/unit/test_github_requirements.py | 30 | ||||
-rw-r--r-- | zuul/driver/github/githubmodel.py | 23 |
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 |