Support regex matching of github status

The github status requirements matching and trigger filter are
currently plain text matching based. This currently limits sharing of
pipeline definitions between tenants as zuul reports the status as
'<tenant>/<pipeline>'. This currently makes it necessary to define
trigger filter for each tenant [1] and completely blocks pipeline
requirements.

A solution to this is regex matching which makes it possible to define
the filter once [2].

Further this enables an interesting further use case to trigger on any
successfull status [3]. This makes it easier to cooperate with other
CI systems or github apps which also set a status.

Directly use re2 as this will be used in the future for regex
matching.

[1] Trigger filter snippet
  trigger:
    github:
      - event: pull_request
        action: status
        status:
          - zuul:tenant1/check:success
          - zuul:tenant2/check:success
          - zuul:tenant3/check:success
          - zuul:tenant4/check:success

[2] Regex trigger filter snippet
  trigger:
    github:
      - event: pull_request
        action: status
        status:
          - zuul:.+/check:success

[3] Generic success filter snippet
  trigger:
    github:
      - event: pull_request
        action: status
        status:
          - .*:success

Change-Id: Id1b9d7334db78d0f13db33d47a80ffdb65f921df
This commit is contained in:
Tobias Henkel 2018-04-13 11:18:49 +02:00
parent 42e35c2adf
commit 0c3b8fb963
7 changed files with 109 additions and 9 deletions

View File

@ -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]

View File

@ -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

View File

@ -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.

View File

@ -24,3 +24,4 @@ iso8601
aiohttp<3.0.0
uvloop;python_version>='3.5'
psutil
fb-re2>=1.0.6

View File

@ -13,6 +13,21 @@
github:
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
@ -28,6 +43,21 @@
github:
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
@ -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

View File

@ -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')

View File

@ -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