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):