From 07cf1e1a6e11181b1abebc35067a9aa20bef4e47 Mon Sep 17 00:00:00 2001 From: Felix Edel Date: Wed, 4 Mar 2020 15:37:28 +0100 Subject: [PATCH] Allow check runs to be configured as required status in pipeline config Although commit checks and statuses can only be retrieved via their respective APIs, Github does not differentiate between both in terms of branch protection and in the status section (below the comments of a PR). To mimic this behaviour in Zuul, one can now configure a check run as required status in the pipeline config. Change-Id: Ia5757c476bcee6082991928ab7c1743d0200d04a --- doc/source/reference/drivers/github.rst | 15 +++++++ ...github-require-check-294d3f27da790fae.yaml | 6 +++ .../fixtures/layouts/requirements-github.yaml | 29 ++++++++++++ tests/unit/test_github_requirements.py | 45 ++++++++++++++++++- zuul/driver/github/githubconnection.py | 11 +++++ 5 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/github-require-check-294d3f27da790fae.yaml diff --git a/doc/source/reference/drivers/github.rst b/doc/source/reference/drivers/github.rst index 474c8ac827..c2c085eca0 100644 --- a/doc/source/reference/drivers/github.rst +++ b/doc/source/reference/drivers/github.rst @@ -499,6 +499,21 @@ enqueued into the pipeline. request. The syntax is ``user:status:value``. This can also be a regular expression. + Zuul does not differentiate between a status reported via + status API or via checks API (which is also how Github behaves + in terms of branch protection and `status checks`__). + Thus, the status could be reported by a + :attr:`pipeline...status` or a + :attr:`pipeline...check`. + + When a status is reported via the status API, Github will add + a ``[bot]`` to the name of the app that reported the status, + resulting in something like ``user[bot]:status:value``. For a + status reported via the checks API, the app's slug will be + used as is. + + .. __: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-status-checks#types-of-status-checks-on-github + .. attr:: label A string value indicating that the pull request must have the diff --git a/releasenotes/notes/github-require-check-294d3f27da790fae.yaml b/releasenotes/notes/github-require-check-294d3f27da790fae.yaml new file mode 100644 index 0000000000..a01d59b20f --- /dev/null +++ b/releasenotes/notes/github-require-check-294d3f27da790fae.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The status pipeline requirements of the Github driver + :attr:`pipeline.require..status` now also matches + on statuses reported via the Github checks API. diff --git a/tests/fixtures/layouts/requirements-github.yaml b/tests/fixtures/layouts/requirements-github.yaml index f5fa0f5de1..9e376b8b6b 100644 --- a/tests/fixtures/layouts/requirements-github.yaml +++ b/tests/fixtures/layouts/requirements-github.yaml @@ -88,6 +88,25 @@ comment: true check: success +- pipeline: + name: require_check_run + manager: independent + require: + github: + status: + # Github does not differentiate between status and check run + # in case of branch protection and required status checks. + - check-run:tenant-one/check:success + trigger: + github: + - event: pull_request + action: comment + comment: trigger me + success: + github: + check: success + + - pipeline: name: trigger manager: independent @@ -367,6 +386,10 @@ name: project15-check-run run: playbooks/project15-check-run.yaml +- job: + name: project16-require-check-run + run: playbooks/project16-require-check-run.yaml + - project: name: org/project1 pipeline: @@ -465,3 +488,9 @@ trigger_check_run: jobs: - project15-check-run + +- project: + name: org/project16 + require_check_run: + jobs: + - project16-require-check-run diff --git a/tests/unit/test_github_requirements.py b/tests/unit/test_github_requirements.py index 461df55dac..e542f80f96 100644 --- a/tests/unit/test_github_requirements.py +++ b/tests/unit/test_github_requirements.py @@ -14,7 +14,7 @@ import time -from tests.base import ZuulTestCase, simple_layout +from tests.base import ZuulGithubAppTestCase, ZuulTestCase, simple_layout class TestGithubRequirements(ZuulTestCase): @@ -588,3 +588,46 @@ class TestGithubRequirements(ZuulTestCase): self.waitUntilSettled() self.assertEqual(len(self.history), 2) self.assertEqual(self.history[1].name, 'project12-status') + + +class TestGithubAppRequirements(ZuulGithubAppTestCase): + """Test pipeline and trigger requirements with app authentication""" + config_file = 'zuul-github-driver.conf' + + @simple_layout("layouts/requirements-github.yaml", driver="github") + def test_pipeline_require_check_run(self): + "Test pipeline requirement: status (reported via a check run)" + project = "org/project16" + github = self.fake_github.getGithubClient() + repo = github.repo_from_project(project) + + A = self.fake_github.openFakePullRequest(project, "master", "A") + # A comment event that we will keep submitting to trigger + comment = A.getCommentAddedEvent("trigger me") + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + + # No status from zuul, so nothing should be enqueued + self.assertEqual(len(self.history), 0) + + # An error check run should also not cause it to be enqueued + repo.create_check_run( + A.head_sha, + "tenant-one/check", + conclusion="failure", + app="check-run", + ) + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # A success check run goes in, ready to be enqueued + repo.create_check_run( + A.head_sha, + "tenant-one/check", + conclusion="success", + app="check-run", + ) + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) diff --git a/zuul/driver/github/githubconnection.py b/zuul/driver/github/githubconnection.py index 19497008d3..a054d9e49c 100644 --- a/zuul/driver/github/githubconnection.py +++ b/zuul/driver/github/githubconnection.py @@ -2085,6 +2085,17 @@ class GithubConnection(BaseConnection): statuses.append("%s:%s:%s" % stuple) seen.append("%s:%s" % (stuple[0], stuple[1])) + # Although Github differentiates commit statuses and commit checks via + # their respective APIs, the branch protection the status section + # (below the comments of a PR) do not differentiate between both. Thus, + # to mimic this behaviour also in Zuul, a required_status in the + # pipeline config could map to either a status or a check. + for check in self.getCommitChecks(project.name, sha, event): + ctuple = _check_as_tuple(check) + if "{}:{}".format(ctuple[0], ctuple[1]) not in seen: + statuses.append("{}:{}:{}".format(*ctuple)) + seen.append("{}:{}".format(ctuple[0], ctuple[1])) + return statuses def getWebController(self, zuul_web):