diff --git a/CHANGELOG b/CHANGELOG index 234c293b..e5762bdc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +## [3.6.0] - 2021-04-27 +# Added +- Support GitHub Action CI thanks to a new build key config `github_actions` + + + ## [3.5.0] - 2021-04-27 # Added - Add "pr_author_options" option to config, which add bypass to a specifique user diff --git a/bert_e/git_host/github/__init__.py b/bert_e/git_host/github/__init__.py index 5981e231..b7b15a03 100644 --- a/bert_e/git_host/github/__init__.py +++ b/bert_e/git_host/github/__init__.py @@ -354,6 +354,12 @@ def get_commit_status(self, ref): combined = AggregatedStatus.get(self.client, owner=self.owner, repo=self.slug, ref=ref) + + actions = AggregatedCheckSuites.get(self.client, + owner=self.owner, + repo=self.slug, ref=ref) + combined.status[actions.key] = actions + except HTTPError as err: if err.response.status_code == 404: return None @@ -501,6 +507,82 @@ def __str__(self) -> str: return self.state +class AggregatedCheckRuns(base.AbstractGitHostObject): + """ + The Endpoint to have access infos about github actions runs + """ + GET_URL = '/repos/{owner}/{repo}/commits/{ref}/check-runs' + SCHEMA = schema.AggregateCheckRuns + + @property + def html_url(self): + if self.data['total_count']: + return self.data['check_runs'][0]['html_url'] + return '' + + +class AggregatedCheckSuites(base.AbstractGitHostObject, + base.AbstractBuildStatus): + """ + The Endpoint to have access infos about github actions suites + """ + GET_URL = '/repos/{owner}/{repo}/commits/{ref}/check-suites' + SCHEMA = schema.AggregateCheckSuites + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._check_suites = [elem for elem in self.data['check_suites']] + + @classmethod + def get(cls, client, url=None, params={}, headers={}, **kwargs): + res = super(AggregatedCheckSuites, cls)\ + .get(client, url, params, headers, **kwargs) + + html_url = AggregatedCheckRuns\ + .get(client, url, params, headers, **kwargs).html_url + res.data['html_url'] = html_url + return res + + @property + def url(self): + return self.data['html_url'] + + @property + def state(self): + queued = any( + elem['status'] == 'queued' for elem in self._check_suites + ) + all_complete = all( + elem['status'] == 'completed' for elem in self._check_suites + ) + all_success = all( + elem['conclusion'] == 'success' for elem in self._check_suites + ) + if self._check_suites.__len__() > 0 and queued: + return 'NOTSTARTED' + elif self._check_suites.__len__() > 0 and not all_complete: + return 'INPROGRESS' + elif self._check_suites.__len__() > 0 and all_complete and all_success: + return 'SUCCESSFUL' + else: + return 'FAILED' + + @property + def description(self) -> str: + return 'github actions CI' + + @property + def key(self) -> str: + return 'github_actions' + + @property + def commit(self): + if self._check_suites.__len__() > 0: + return self._check_suites[0]["head_sha"] + else: + return None + + class PullRequest(base.AbstractGitHostObject, base.AbstractPullRequest): LIST_URL = '/repos/{owner}/{repo}/pulls' GET_URL = '/repos/{owner}/{repo}/pulls/{number}' diff --git a/bert_e/git_host/github/schema.py b/bert_e/git_host/github/schema.py index 89031b63..d103a91d 100644 --- a/bert_e/git_host/github/schema.py +++ b/bert_e/git_host/github/schema.py @@ -78,6 +78,31 @@ class Branch(Schema): repo = fields.Nested(Repo) +class CheckSuites(Schema): + id = fields.Integer() + head_sha = fields.Str() + status = fields.Str() + conclusion = fields.Str(allow_none=True) + + +class AggregateCheckSuites(Schema): + total_count = fields.Integer() + check_suites = fields.Nested(CheckSuites, many=True) + + +class CheckRuns(Schema): + id = fields.Integer() + head_sha = fields.Str() + status = fields.Str() + conclusion = fields.Str(allow_none=True) + html_url = fields.Url() + + +class AggregateCheckRuns(Schema): + total_count = fields.Integer() + check_runs = fields.Nested(CheckRuns, many=True) + + class PullRequest(Schema): number = fields.Int(required=True) url = fields.Url() diff --git a/bert_e/tests/assets/workflow-error.yml b/bert_e/tests/assets/workflow-error.yml new file mode 100644 index 00000000..766771d8 --- /dev/null +++ b/bert_e/tests/assets/workflow-error.yml @@ -0,0 +1,3 @@ +name: basic tests + +on: diff --git a/bert_e/tests/assets/workflow-fail.yml b/bert_e/tests/assets/workflow-fail.yml new file mode 100644 index 00000000..658a7c0b --- /dev/null +++ b/bert_e/tests/assets/workflow-fail.yml @@ -0,0 +1,11 @@ +name: basic tests + +on: push + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - run: exit 1 diff --git a/bert_e/tests/assets/workflow.yml b/bert_e/tests/assets/workflow.yml new file mode 100644 index 00000000..c8eba648 --- /dev/null +++ b/bert_e/tests/assets/workflow.yml @@ -0,0 +1,20 @@ +name: basic tests + +on: push + +jobs: + first: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - run: exit 0 + + second: + needs: + - first + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - run: exit 0 diff --git a/bert_e/tests/test_bert_e.py b/bert_e/tests/test_bert_e.py index a6c82110..1aba2dfd 100644 --- a/bert_e/tests/test_bert_e.py +++ b/bert_e/tests/test_bert_e.py @@ -14,6 +14,7 @@ import argparse import logging +import os.path import re import sys import time @@ -157,13 +158,20 @@ def create_branch(repo, name, from_branch=None, file_=False, do_push=True): add_file_to_branch(repo, name, file_, do_push) -def add_file_to_branch(repo, branch_name, file_name, do_push=True): - repo.cmd('git checkout ' + branch_name) +def add_file_to_branch(repo, branch_name, file_name, do_push=True, folder='.'): if file_name is True: file_name = 'file_created_on_' + branch_name.replace('/', '_') - repo.cmd('echo %s > %s' % (branch_name, file_name)) - repo.cmd('git add ' + file_name) - repo.cmd('git commit -m "adds %s file on %s"' % (file_name, branch_name)) + + file_path = os.path.dirname(os.path.abspath(__file__)) + + repo.cmd(f'git checkout {branch_name}') + repo.cmd(f'mkdir -p {folder}') + if os.path.isfile(f'{file_path}/assets/{file_name}'): + repo.cmd(f'cp {file_path}/assets/{file_name} {folder}/{file_name}') + else: + repo.cmd(f'echo {branch_name} > {file_name}') + repo.cmd(f'git add {folder}/{file_name}') + repo.cmd(f'git commit -m "adds %s file on %s"' % (file_name, branch_name)) if do_push: repo.cmd('git pull || exit 0') repo.cmd('git push --set-upstream origin ' + branch_name) @@ -3187,6 +3195,126 @@ def test_source_branch_history_changed(self): options=self.bypass_all_but(['bypass_build_status']), backtrace=True) + def test_github_actions_result_error(self): + if self.args.git_host != 'github': + self.skipTest("GitHub Actions is only supported with GitHub") + + settings = """ +repository_owner: {owner} +repository_slug: {slug} +repository_host: {host} +robot: {robot} +robot_email: nobody@nowhere.com +pull_request_base_url: https://bitbucket.org/{owner}/{slug}/bar/pull-requests/{{pr_id}} +commit_base_url: https://bitbucket.org/{owner}/{slug}/commits/{{commit_id}} +build_key: github_actions +always_create_integration_pull_requests: False +""" # noqa + pr = self.create_pr('bugfix/TEST-00001', 'development/4.3') + add_file_to_branch(self.gitrepo, + 'bugfix/TEST-00001', + 'workflow-error.yml', + do_push=True, + folder='.github/workflows') + add_file_to_branch(self.gitrepo, + 'bugfix/TEST-00001', + 'workflow.yml', + do_push=True, + folder='.github/workflows') + + for index in range(0, 60): + try: + with self.assertRaises(exns.BuildFailed): + self.handle( + pr.id, settings=settings, + options=self.bypass_all_but(['bypass_build_status']), + backtrace=True) + except Exception: + if index == 59: + raise exns.BuildFailed() + time.sleep(2) + else: + break + + def test_github_actions_result_fail(self): + """ + To check the return of github action we are adding one workflow + that will succeed and an other with a failure. + We should have an build status error at the end. + """ + if self.args.git_host != 'github': + self.skipTest("GitHub Actions is only supported with GitHub") + + settings = """ +repository_owner: {owner} +repository_slug: {slug} +repository_host: {host} +robot: {robot} +robot_email: nobody@nowhere.com +pull_request_base_url: https://bitbucket.org/{owner}/{slug}/bar/pull-requests/{{pr_id}} +commit_base_url: https://bitbucket.org/{owner}/{slug}/commits/{{commit_id}} +build_key: github_actions +always_create_integration_pull_requests: False +""" # noqa + pr = self.create_pr('bugfix/TEST-00001', 'development/4.3') + add_file_to_branch(self.gitrepo, + 'bugfix/TEST-00001', + 'workflow-fail.yml', + do_push=True, + folder='.github/workflows') + + for index in range(0, 60): + try: + with self.assertRaises(exns.BuildFailed): + self.handle( + pr.id, settings=settings, + options=self.bypass_all_but(['bypass_build_status']), + backtrace=True) + + except Exception: + if index == 59: + raise exns.BuildFailed() + time.sleep(4) + else: + break + + def test_github_actions_result(self): + if self.args.git_host != 'github': + self.skipTest("GitHub Actions is only supported with GitHub") + + settings = """ +repository_owner: {owner} +repository_slug: {slug} +repository_host: {host} +robot: {robot} +robot_email: nobody@nowhere.com +pull_request_base_url: https://bitbucket.org/{owner}/{slug}/bar/pull-requests/{{pr_id}} +commit_base_url: https://bitbucket.org/{owner}/{slug}/commits/{{commit_id}} +build_key: github_actions +always_create_integration_pull_requests: False +""" # noqa + pr = self.create_pr('bugfix/TEST-00001', 'development/4.3') + add_file_to_branch(self.gitrepo, + 'bugfix/TEST-00001', + 'workflow.yml', + do_push=True, + folder='.github/workflows') + + for index in range(0, 60): + try: + with self.assertRaises(exns.SuccessMessage): + self.handle( + pr.id, settings=settings, + options=self.bypass_all_but(['bypass_build_status']), + backtrace=True) + + except Exception: + if index == 59: + raise exns.BuildFailed() + time.sleep(2) + else: + break + def test_source_branch_commit_added_and_target_updated(self): pr = self.create_pr('bugfix/TEST-00001', 'development/4.3') pr2 = self.create_pr('bugfix/TEST-00002', 'development/4.3') diff --git a/settings.sample.yml b/settings.sample.yml index eb066643..82fd4c0d 100644 --- a/settings.sample.yml +++ b/settings.sample.yml @@ -60,6 +60,7 @@ commit_base_url: https://bitbucket.org/foo/bar/commits/{commit_id} # build_key [OPTIONAL]: # The label of the key to look for in githost commit statuses. +# The key "github_actions" can be set to check github actions status instead of external app checks # # default value: pre-merge #