Add "draft" github pipeline requirement

This adds the "draft" PR status as a pipeline requirement to the
GitHub driver.  It is already used implicitly in dependent pipelines,
but this will allow it to be added explicitly to other pipelines
(for example, check).

This also fixes some minor copy/pasta errors in debug messages related
to github pipeline requirements.

Change-Id: I05f8f61aee251af24c1479274904b429baedb29d
This commit is contained in:
James E. Blair
2022-10-10 10:53:14 -07:00
parent 51aeec13e6
commit 7aba198bed
6 changed files with 135 additions and 11 deletions

View File

@@ -551,6 +551,12 @@ enqueued into the pipeline.
.. TODO: this could probably be expanded upon -- under what
circumstances might this happen with github
.. attr:: draft
A boolean value (``true`` or ``false``) that indicates whether
or not the change must be marked as a draft in GitHub in order
to be enqueued.
.. attr:: status
A string value that corresponds with the status of the pull

View File

@@ -0,0 +1,6 @@
---
features:
- |
The GitHub driver now supports specifying the `draft` status of a
PR as a pipeline requirement.
See :attr:`pipeline.require.<github source>.draft`.

View File

@@ -285,6 +285,34 @@
github:
comment: true
- pipeline:
name: require_draft
manager: independent
require:
github:
draft: true
trigger:
github:
- event: pull_request
action: changed
success:
github:
comment: true
- pipeline:
name: reject_draft
manager: independent
reject:
github:
draft: true
trigger:
github:
- event: pull_request
action: changed
success:
github:
comment: true
- pipeline:
name: require_label
manager: independent
@@ -390,6 +418,14 @@
name: project16-require-check-run
run: playbooks/project16-require-check-run.yaml
- job:
name: project17-require-draft
run: playbooks/project17-require-draft.yaml
- job:
name: project18-reject-draft
run: playbooks/project18-reject-draft.yaml
- project:
name: org/project1
pipeline:
@@ -494,3 +530,15 @@
require_check_run:
jobs:
- project16-require-check-run
- project:
name: org/project17
require_draft:
jobs:
- project17-require-draft
- project:
name: org/project18
reject_draft:
jobs:
- project18-reject-draft

View File

@@ -502,6 +502,46 @@ class TestGithubRequirements(ZuulTestCase):
# Event hash is not current, should trigger
self.assertEqual(len(self.history), 1)
@simple_layout('layouts/requirements-github.yaml', driver='github')
def test_require_draft(self):
A = self.fake_github.openFakePullRequest('org/project17', 'master',
'A', draft=True)
# A sync event that we will keep submitting to trigger
sync = A.getPullRequestSynchronizeEvent()
self.fake_github.emitEvent(sync)
self.waitUntilSettled()
# PR is a draft, should enqueue
self.assertEqual(len(self.history), 1)
# Make the PR not a draft
A.draft = False
self.fake_github.emitEvent(sync)
self.waitUntilSettled()
# PR is not a draft, should not enqueue
self.assertEqual(len(self.history), 1)
@simple_layout('layouts/requirements-github.yaml', driver='github')
def test_reject_draft(self):
A = self.fake_github.openFakePullRequest('org/project18', 'master',
'A', draft=True)
# A sync event that we will keep submitting to trigger
sync = A.getPullRequestSynchronizeEvent()
self.fake_github.emitEvent(sync)
self.waitUntilSettled()
# PR is a draft, should not enqueue
self.assertEqual(len(self.history), 0)
# Make the PR not a draft
A.draft = False
self.fake_github.emitEvent(sync)
self.waitUntilSettled()
# PR is not a draft, should enqueue
self.assertEqual(len(self.history), 1)
@simple_layout('layouts/requirements-github.yaml', driver='github')
def test_pipeline_require_label(self):
"Test pipeline requirement: label"

View File

@@ -455,11 +455,12 @@ class GithubEventFilter(EventFilter, GithubCommonFilter):
class GithubRefFilter(RefFilter, GithubCommonFilter):
def __init__(self, connection_name, statuses=[], required_reviews=[],
reject_reviews=[], open=None, merged=None,
current_patchset=None, reject_open=None, reject_merged=None,
reject_current_patchset=None, labels=[], reject_labels=[],
reject_statuses=[]):
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,
@@ -479,6 +480,10 @@ class GithubRefFilter(RefFilter, GithubCommonFilter):
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
@@ -496,12 +501,14 @@ class GithubRefFilter(RefFilter, GithubCommonFilter):
if self.reject_reviews:
ret += (' reject-reviews: %s' %
str(self.reject_reviews))
if self.open:
if self.open is not None:
ret += ' open: %s' % self.open
if self.merged:
if self.merged is not None:
ret += ' merged: %s' % self.merged
if self.current_patchset:
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:
@@ -521,7 +528,8 @@ class GithubRefFilter(RefFilter, GithubCommonFilter):
# and cannot possibly pass this test.
if hasattr(change, 'number'):
if self.open != change.open:
return FalseWithReason("Change is not a PR")
return FalseWithReason(
"Change does not match open requirement")
else:
return FalseWithReason("Change is not a PR")
@@ -530,7 +538,8 @@ class GithubRefFilter(RefFilter, GithubCommonFilter):
# and cannot possibly pass this test.
if hasattr(change, 'number'):
if self.merged != change.is_merged:
return FalseWithReason("Change is not a PR")
return FalseWithReason(
"Change does not match merged requirement")
else:
return FalseWithReason("Change is not a PR")
@@ -539,7 +548,18 @@ class GithubRefFilter(RefFilter, GithubCommonFilter):
# and cannot possibly pass this test.
if hasattr(change, 'number'):
if self.current_patchset != change.is_current_patchset:
return FalseWithReason("Change is not current")
return FalseWithReason(
"Change does not match current-patchset requirement")
else:
return FalseWithReason("Change is not a PR")
if self.draft is not None:
# if a "change" has no number, it's not a change, but a push
# and cannot possibly pass this test.
if hasattr(change, 'number'):
if self.draft != change.draft:
return FalseWithReason(
"Change does not match draft requirement")
else:
return FalseWithReason("Change is not a PR")

View File

@@ -169,6 +169,7 @@ class GithubSource(BaseSource):
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]
@@ -182,6 +183,7 @@ class GithubSource(BaseSource):
reject_open=config.get('open'),
reject_merged=config.get('merged'),
reject_current_patchset=config.get('current-patchset'),
reject_draft=config.get('draft'),
)
return [f]
@@ -207,6 +209,7 @@ def getRequireSchema():
'open': bool,
'merged': bool,
'current-patchset': bool,
'draft': bool,
'label': scalar_or_list(str)}
return require
@@ -217,5 +220,6 @@ def getRejectSchema():
'open': bool,
'merged': bool,
'current-patchset': bool,
'draft': bool,
'label': scalar_or_list(str)}
return reject