Implement pipeline requirement of github labels
Projects that do not use github reviews may wish to instead use github
labels as a way to trigger pipelines. As such, these projects may also
wish to require labels exist on a pull request when processing other
events.
Change-Id: I8f73c438c58db38790ea7e5bf435fbda01324e77
Story: 2000774
Task: 4632
diff --git a/tests/base.py b/tests/base.py
index 93b5785..4a66de6 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -936,7 +936,8 @@
'full_name': pr.project
}
},
- 'files': pr.files
+ 'files': pr.files,
+ 'labels': pr.labels
}
return data
diff --git a/tests/fixtures/layouts/requirements-github.yaml b/tests/fixtures/layouts/requirements-github.yaml
index 9933f27..891a366 100644
--- a/tests/fixtures/layouts/requirements-github.yaml
+++ b/tests/fixtures/layouts/requirements-github.yaml
@@ -168,6 +168,21 @@
github:
comment: true
+- pipeline:
+ name: require_label
+ manager: independent
+ require:
+ github:
+ label: approved
+ trigger:
+ github:
+ - event: pull_request
+ action: comment
+ comment: 'test me'
+ success:
+ github:
+ comment: true
+
- job:
name: project1-pipeline
- job:
@@ -186,6 +201,8 @@
name: project8-requireopen
- job:
name: project9-requirecurrent
+- job:
+ name: project10-label
- project:
name: org/project1
@@ -243,3 +260,9 @@
require_current:
jobs:
- project9-requirecurrent
+
+- project:
+ name: org/project10
+ require_label:
+ jobs:
+ - project10-label
diff --git a/tests/unit/test_github_requirements.py b/tests/unit/test_github_requirements.py
index 43bdfc2..135f7ab 100644
--- a/tests/unit/test_github_requirements.py
+++ b/tests/unit/test_github_requirements.py
@@ -350,3 +350,28 @@
self.waitUntilSettled()
# Event hash is not current, should not trigger
self.assertEqual(len(self.history), 1)
+
+ @simple_layout('layouts/requirements-github.yaml', driver='github')
+ def test_pipeline_require_label(self):
+ "Test pipeline requirement: label"
+ A = self.fake_github.openFakePullRequest('org/project10', 'master',
+ 'A')
+ # A comment event that we will keep submitting to trigger
+ comment = A.getCommentAddedEvent('test me')
+ self.fake_github.emitEvent(comment)
+ self.waitUntilSettled()
+ # No label so should not be enqueued
+ self.assertEqual(len(self.history), 0)
+
+ # A derp label should not cause it to be enqueued
+ A.addLabel('derp')
+ 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, 'project10-label')
diff --git a/zuul/driver/github/githubconnection.py b/zuul/driver/github/githubconnection.py
index f9f1c27..fac000c 100644
--- a/zuul/driver/github/githubconnection.py
+++ b/zuul/driver/github/githubconnection.py
@@ -524,6 +524,7 @@
change.patchset)
change.reviews = self.getPullReviews(change.project,
change.number)
+ change.labels = change.pr.get('labels')
return change
@@ -572,8 +573,11 @@
self.log.warning("Pull request #%s of %s/%s returned None!" % (
number, owner, proj))
time.sleep(1)
+ # Get the issue obj so we can get the labels (this is silly)
+ issueobj = probj.issue()
pr = probj.as_dict()
pr['files'] = [f.filename for f in probj.files()]
+ pr['labels'] = [l.name for l in issueobj.labels()]
log_rate_limit(self.log, github)
return pr
diff --git a/zuul/driver/github/githubmodel.py b/zuul/driver/github/githubmodel.py
index cfd1bc0..db119f0 100644
--- a/zuul/driver/github/githubmodel.py
+++ b/zuul/driver/github/githubmodel.py
@@ -28,9 +28,13 @@
class PullRequest(Change):
def __init__(self, project):
super(PullRequest, self).__init__(project)
+ self.project = None
+ self.pr = None
self.updated_at = None
self.title = None
self.reviews = []
+ self.files = []
+ self.labels = []
def isUpdateOf(self, other):
if (hasattr(other, 'number') and self.number == other.number and
@@ -284,7 +288,8 @@
class GithubRefFilter(RefFilter, GithubCommonFilter):
def __init__(self, connection_name, statuses=[], required_reviews=[],
- reject_reviews=[], open=None, current_patchset=None):
+ reject_reviews=[], open=None, current_patchset=None,
+ labels=[]):
RefFilter.__init__(self, connection_name)
GithubCommonFilter.__init__(self, required_reviews=required_reviews,
@@ -293,6 +298,7 @@
self.statuses = statuses
self.open = open
self.current_patchset = current_patchset
+ self.labels = labels
def __repr__(self):
ret = '<GithubRefFilter'
@@ -310,6 +316,8 @@
ret += ' open: %s' % self.open
if self.current_patchset:
ret += ' current-patchset: %s' % self.current_patchset
+ if self.labels:
+ ret += ' labels: %s' % self.labels
ret += '>'
@@ -341,4 +349,9 @@
if not self.matchesReviews(change):
return False
+ # required labels are ANDed
+ for label in self.labels:
+ if label not in change.labels:
+ return False
+
return True
diff --git a/zuul/driver/github/githubsource.py b/zuul/driver/github/githubsource.py
index 519ebf1..1bd280f 100644
--- a/zuul/driver/github/githubsource.py
+++ b/zuul/driver/github/githubsource.py
@@ -97,6 +97,7 @@
required_reviews=to_list(config.get('review')),
open=config.get('open'),
current_patchset=config.get('current-patchset'),
+ labels=to_list(config.get('label')),
)
return [f]
@@ -121,7 +122,8 @@
require = {'status': scalar_or_list(str),
'review': scalar_or_list(review),
'open': bool,
- 'current-patchset': bool}
+ 'current-patchset': bool,
+ 'label': scalar_or_list(str)}
return require