diff --git a/buildbot_gitea/reporter.py b/buildbot_gitea/reporter.py new file mode 100644 index 0000000..ce887e1 --- /dev/null +++ b/buildbot_gitea/reporter.py @@ -0,0 +1,166 @@ +# Based on the gitlab reporter from buildbot + +from __future__ import absolute_import +from __future__ import print_function + +from twisted.internet import defer +from twisted.python import log + +from buildbot.process.properties import Interpolate +from buildbot.process.properties import Properties +from buildbot.process.results import CANCELLED +from buildbot.process.results import EXCEPTION +from buildbot.process.results import FAILURE +from buildbot.process.results import RETRY +from buildbot.process.results import SKIPPED +from buildbot.process.results import SUCCESS +from buildbot.process.results import WARNINGS +from buildbot.reporters import http +from buildbot.util import httpclientservice +from buildbot.util import unicode2NativeString + + +class GiteaStatusPush(http.HttpStatusPushBase): + name = "GiteaStatusPush" + neededDetails = dict(wantProperties=True) + + @defer.inlineCallbacks + def reconfigService(self, baseURL, token, + startDescription=None, endDescription=None, + context=None, context_pr=None, verbose=False, + warningAsSuccess=False, **kwargs): + + token = yield self.renderSecrets(token) + yield http.HttpStatusPushBase.reconfigService(self, **kwargs) + + self.context = context or Interpolate('buildbot/%(prop:buildername)s') + self.context_pr = context_pr or \ + Interpolate('buildbot/pull_request/%(prop:buildername)s') + self.startDescription = startDescription or 'Build started.' + self.endDescription = endDescription or 'Build done.' + if baseURL.endswith('/'): + baseURL = baseURL[:-1] + self.baseURL = baseURL + self._http = yield httpclientservice.HTTPClientService.getService( + self.master, baseURL, + headers={'AuthorizationHeaderToken': 'token {}'.format(token)}, + debug=self.debug, verify=self.verify) + self.verbose = verbose + self.project_ids = {} + self.warningAsSuccess = warningAsSuccess + + def createStatus(self, + project_owner, repo_name, sha, state, target_url=None, + description=None, context=None): + """ + :param project_owner: username of the owning user or organization + :param repo_name: name of the repository + :param sha: Full sha to create the status for. + :param state: one of the following 'pending', 'success', 'failed' + or 'cancelled'. + :param target_url: Target url to associate with this status. + :param description: Short description of the status. + :param context: Context of the result + :return: A deferred with the result from GitLab. + + """ + payload = {'state': state} + + if description is not None: + payload['description'] = description + + if target_url is not None: + payload['target_url'] = target_url + + if context is not None: + payload['name'] = context + + return self._http.post( + '/api/v1/repos/{owner}/{repository}/statuses/{sha}'.format( + owner=project_owner, + repository=repo_name, + sha=sha + ), + json=payload) + + @defer.inlineCallbacks + def send(self, build): + props = Properties.fromDict(build['properties']) + props.master = self.master + + if build['complete']: + state = { + SUCCESS: 'success', + WARNINGS: 'success' if self.warningAsSuccess else 'warning', + FAILURE: 'failure', + SKIPPED: 'success', + EXCEPTION: 'error', + RETRY: 'pending', + CANCELLED: 'error' + }.get(build['results'], 'failure') + description = yield props.render(self.endDescription) + else: + state = 'pending' + description = yield props.render(self.startDescription) + + if 'pr_id' in props: + context = yield props.render(self.context_pr) + else: + context = yield props.render(self.context) + + sourcestamps = build['buildset']['sourcestamps'] + + for sourcestamp in sourcestamps: + sha = sourcestamp['revision'] + if 'repository_name' in props: + repository_name = props['repository_name'] + else: + log.msg( + "Could not send status, " + "build has no repository_name property for Gitea.") + continue + if 'owner' in props: + repository_owner = props['owner'] + else: + log.msg( + "Could not send status, " + "build has no owner property for Gitea.") + continue + try: + sha = unicode2NativeString(sha) + state = unicode2NativeString(state) + target_url = unicode2NativeString(build['url']) + context = unicode2NativeString(context) + description = unicode2NativeString(description) + res = yield self.createStatus( + project_owner=repository_owner, + repo_name=repository_name, + sha=sha, + state=state, + target_url=target_url, + context=context, + description=description + ) + if res.code not in (200, 201, 204): + message = yield res.json() + message = message.get('message', 'unspecified error') + log.msg( + 'Could not send status "{state}" for ' + '{repo} at {sha}: {message}'.format( + state=state, + repo=sourcestamp['repository'], sha=sha, + message=message)) + elif self.verbose: + log.msg( + 'Status "{state}" sent for ' + '{repo} at {sha}.'.format( + state=state, + repo=sourcestamp['repository'], sha=sha)) + except Exception as e: + log.err( + e, + 'Failed to send status "{state}" for ' + '{repo} at {sha}'.format( + state=state, + repo=sourcestamp['repository'], sha=sha + )) diff --git a/buildbot_gitea/test/test_reporter.py b/buildbot_gitea/test/test_reporter.py new file mode 100644 index 0000000..8f6ce9b --- /dev/null +++ b/buildbot_gitea/test/test_reporter.py @@ -0,0 +1,162 @@ +# Based on TestGitLabStatusPush from buildbot + +from __future__ import absolute_import +from __future__ import print_function + +from mock import Mock + +from twisted.internet import defer +from twisted.trial import unittest + +from buildbot import config +from buildbot.process.properties import Interpolate +from buildbot.process.results import FAILURE +from buildbot.process.results import SUCCESS +from buildbot_gitea.reporter import GiteaStatusPush +from buildbot.test.fake import fakemaster +from buildbot.test.fake import httpclientservice as fakehttpclientservice +from buildbot.test.util import logging +from buildbot.test.util.reporter import ReporterTestMixin + + +class TestGiteaStatusPush( + unittest.TestCase, + ReporterTestMixin, + logging.LoggingMixin): + # repository must be in the form http://gitea// + TEST_REPO = u'http://gitea/buildbot/buildbot' + + @defer.inlineCallbacks + def setUp(self): + # ignore config error if txrequests is not installed + self.patch(config, '_errors', Mock()) + self.master = fakemaster.make_master(testcase=self, + wantData=True, wantDb=True, wantMq=True) + + yield self.master.startService() + self._http = yield fakehttpclientservice.HTTPClientService.getFakeService( + self.master, self, + "http://gitea", headers={'AuthorizationHeaderToken': 'token XXYYZZ'}, + debug=None, verify=None) + self.sp = sp = GiteaStatusPush("http://gitea/", Interpolate('XXYYZZ')) + sp.sessionFactory = Mock(return_value=Mock()) + + yield sp.setServiceParent(self.master) + + def tearDown(self): + return self.master.stopService() + + def setupProps(self): + self.TEST_PROPS['owner'] = "buildbot" + self.TEST_PROPS['repository_name'] = "buildbot" + + @defer.inlineCallbacks + def setupBuildResults(self, buildResults): + self.insertTestData([buildResults], buildResults) + build = yield self.master.data.get(("builds", 20)) + defer.returnValue(build) + + @defer.inlineCallbacks + def test_basic(self): + self.setupProps() + build = yield self.setupBuildResults(SUCCESS) + # we make sure proper calls to txrequests have been made + self._http.expect( + 'post', + '/api/v1/repos/buildbot/buildbot/statuses/d34db33fd43db33f', + json={'state': 'pending', + 'target_url': 'http://localhost:8080/#builders/79/builds/0', + 'description': 'Build started.', 'name': 'buildbot/Builder0'}) + self._http.expect( + 'post', + '/api/v1/repos/buildbot/buildbot/statuses/d34db33fd43db33f', + json={'state': 'success', + 'target_url': 'http://localhost:8080/#builders/79/builds/0', + 'description': 'Build done.', 'name': 'buildbot/Builder0'}) + self._http.expect( + 'post', + '/api/v1/repos/buildbot/buildbot/statuses/d34db33fd43db33f', + json={'state': 'failure', + 'target_url': 'http://localhost:8080/#builders/79/builds/0', + 'description': 'Build done.', 'name': 'buildbot/Builder0'}) + + build['complete'] = False + self.sp.buildStarted(("build", 20, "started"), build) + build['complete'] = True + self.sp.buildFinished(("build", 20, "finished"), build) + build['results'] = FAILURE + self.sp.buildFinished(("build", 20, "finished"), build) + + @defer.inlineCallbacks + def test_sshurl(self): + self.setupProps() + self.TEST_REPO = u'git@gitea:buildbot/buildbot.git' + build = yield self.setupBuildResults(SUCCESS) + # we make sure proper calls to txrequests have been made + self._http.expect( + 'post', + '/api/v1/repos/buildbot/buildbot/statuses/d34db33fd43db33f', + json={'state': 'pending', + 'target_url': 'http://localhost:8080/#builders/79/builds/0', + 'description': 'Build started.', 'name': 'buildbot/Builder0'}) + build['complete'] = False + self.sp.buildStarted(("build", 20, "started"), build) + + @defer.inlineCallbacks + def test_noowner(self): + self.setUpLogging() + self.setupProps() + del self.TEST_PROPS["owner"] + self.TEST_REPO = u'' + build = yield self.setupBuildResults(SUCCESS) + build['complete'] = False + self.sp.buildStarted(("build", 20, "started"), build) + # implicit check that no http request is done + self.assertLogged("Could not send status, " + "build has no owner property for Gitea.") + + @defer.inlineCallbacks + def test_noreponame(self): + self.setUpLogging() + self.setupProps() + del self.TEST_PROPS["repository_name"] + self.TEST_REPO = u'' + build = yield self.setupBuildResults(SUCCESS) + build['complete'] = False + self.sp.buildStarted(("build", 20, "started"), build) + # implicit check that no http request is done + self.assertLogged("Could not send status, " + "build has no repository_name property for Gitea.") + + @defer.inlineCallbacks + def test_senderror(self): + self.setupProps() + self.setUpLogging() + build = yield self.setupBuildResults(SUCCESS) + # we make sure proper calls to txrequests have been made + self._http.expect( + 'post', + '/api/v1/repos/buildbot/buildbot/statuses/d34db33fd43db33f', + json={'state': 'pending', + 'target_url': 'http://localhost:8080/#builders/79/builds/0', + 'description': 'Build started.', 'name': 'buildbot/Builder0'}, + content_json={'message': 'sha1 not found for branch master'}, + code=404) + build['complete'] = False + self.sp.buildStarted(("build", 20, "started"), build) + self.assertLogged( + "Could not send status \"pending\" for " + "http://gitea/buildbot/buildbot at d34db33fd43db33f:" + " sha1 not found for branch master") + + @defer.inlineCallbacks + def test_badchange(self): + self.setupProps() + self.setUpLogging() + build = yield self.setupBuildResults(SUCCESS) + # we make sure proper calls to txrequests have been made + build['complete'] = False + self.sp.buildStarted(("build", 20, "started"), build) + self.assertLogged("Failed to send status \"pending\" for" + " http://gitea/buildbot/buildbot at d34db33fd43db33f") + self.flushLoggedErrors(AssertionError) diff --git a/buildbot_gitea/webhook.py b/buildbot_gitea/webhook.py index 0795981..4625324 100644 --- a/buildbot_gitea/webhook.py +++ b/buildbot_gitea/webhook.py @@ -43,6 +43,8 @@ class GiteaHandler(BaseHookHandler): 'category': event_type, 'properties': { 'event': event_type, + 'repository_name': repository['name'], + 'owner': repository["owner"]["username"] }, } if codebase is not None: @@ -97,6 +99,8 @@ class GiteaHandler(BaseHookHandler): 'head_git_ssh_url': head['repo']['ssh_url'], 'pr_id': pull_request['id'], 'pr_number': pull_request['number'], + 'repository_name': repository['name'], + 'owner': repository["owner"]["username"], }, } if codebase is not None: