Compare commits

..

2 Commits

Author SHA1 Message Date
Marvin Pohl
de9d594758 Check if we are actually the repo for the specified build when PR info is in the request 2021-07-05 13:17:29 +02:00
Marvin Pohl
8decb35652 Use the HEAD sha, when reporting a PR issue 2021-07-05 13:16:15 +02:00
12 changed files with 725 additions and 174 deletions

View File

@ -1,8 +1,9 @@
# Buildbot Transifex Plugin # Buildbot Gitea Plugin
[![PyPI version](https://badge.fury.io/py/buildbot-gitea.svg)](https://badge.fury.io/py/buildbot-gitea) [![PyPI version](https://badge.fury.io/py/buildbot-gitea.svg)](https://badge.fury.io/py/buildbot-gitea)
![GitHub](https://img.shields.io/github/license/lab132/buildbot-gitea) ![GitHub](https://img.shields.io/github/license/lab132/buildbot-gitea)
[![Build Status](https://travis-ci.org/lab132/buildbot-gitea.svg?branch=master)](https://travis-ci.org/lab132/buildbot-gitea)
This plugin for buildbot adds integration support with gitea, featuring push hooks, commit status updates and a change source. This plugin for buildbot adds integration support with gitea, featuring push hooks, commit status updates and a change source.
@ -150,4 +151,4 @@ c['www']['auth'] = util.GiteaAuth(
Resources: Resources:
+ [Gitea OAuth2 Provider documentation](https://docs.gitea.io/en-us/oauth2-provider/) + [Gitea OAuth2 Provider documentation](https://docs.gitea.io/en-us/oauth2-provider/)
+ [Buildbot OAuth2 documentation](https://docs.buildbot.net/current/developer/cls-auth.html?highlight=oauth2#buildbot.www.oauth2.OAuth2Auth) + [Buildbot OAuth2 documentation](https://docs.buildbot.net/current/developer/cls-auth.html?highlight=oauth2#buildbot.www.oauth2.OAuth2Auth)

19
buildbot_gitea/auth.py Normal file
View File

@ -0,0 +1,19 @@
from buildbot.www.oauth2 import OAuth2Auth
from urllib.parse import urljoin
class GiteaAuth(OAuth2Auth):
name = 'Gitea'
faIcon = 'mug-tea'
AUTH_URL = 'login/oauth/authorize'
TOKEN_URL = 'login/oauth/access_token'
def __init__(self, endpoint, client_id, client_secret):
super(GiteaAuth, self).__init__(client_id, client_secret)
self.resourceEndpoint = endpoint
self.authUri = urljoin(endpoint, self.AUTH_URL)
self.tokenUri = urljoin(endpoint, self.TOKEN_URL)
def getUserInfoFromOAuthClient(self, c):
return self.get(c, '/api/v1/user')

229
buildbot_gitea/reporter.py Normal file
View File

@ -0,0 +1,229 @@
# 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.warnings import warn_deprecated
from buildbot.reporters.generators.build import BuildStartEndStatusGenerator
from buildbot.reporters.message import MessageFormatterRenderable
import re
class GiteaStatusPush(http.ReporterBase):
name = "GiteaStatusPush"
ssh_url_match = re.compile(r"(ssh://)?[\w+\-\_]+@[\w\.\-\_]+:?(\d*/)?(?P<owner>[\w_\-\.]+)/(?P<repo_name>[\w_\-\.]+?)(\.git)?$")
def checkConfig(self, baseURL, token,
context=None, context_pr=None, verbose=False,
debug=None, verify=None,
generators=None,
warningAsSuccess=False, **kwargs):
if generators is None:
generators = self._create_default_generators()
super().checkConfig(generators=generators, **kwargs)
httpclientservice.HTTPClientService.checkAvailable(
self.__class__.__name__)
@defer.inlineCallbacks
def reconfigService(self, baseURL, token,
context=None, context_pr=None, verbose=False,
debug=None, verify=None,
generators=None,
warningAsSuccess=False, **kwargs):
token = yield self.renderSecrets(token)
self.debug = debug
self.verify = verify
self.verbose = verbose
if generators is None:
generators = self._create_default_generators()
yield super().reconfigService(generators=generators, **kwargs)
self.context = context or Interpolate('buildbot/%(prop:buildername)s')
self.context_pr = context_pr or \
Interpolate('buildbot/pull_request/%(prop:buildername)s')
if baseURL.endswith('/'):
baseURL = baseURL[:-1]
self.baseURL = baseURL
self._http = yield httpclientservice.HTTPClientService.getService(
self.master, baseURL,
headers={'Authorization': 'token {}'.format(token)},
debug=self.debug, verify=self.verify)
self.verbose = verbose
self.project_ids = {}
self.warningAsSuccess = warningAsSuccess
def _create_default_generators(self):
start_formatter = MessageFormatterRenderable('Build started.')
end_formatter = MessageFormatterRenderable('Build done.')
return [
BuildStartEndStatusGenerator(start_formatter=start_formatter,
end_formatter=end_formatter)
]
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['context'] = 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):
# the only case when this function is called is when the user derives this class, overrides
# send() and calls super().send(build) from there.
yield self._send_impl(build)
@defer.inlineCallbacks
def sendMessage(self, reports):
build = reports[0]['builds'][0]
if self.send.__func__ is not GiteaStatusPush.send:
warn_deprecated('2.9.0', 'send() in reporters has been deprecated. Use sendMessage()')
yield self.send(build)
else:
yield self._send_impl(reports)
@defer.inlineCallbacks
def _send_impl(self, reports):
report = reports[0]
build = report['builds'][0]
props = Properties.fromDict(build['properties'])
props.master = self.master
description = report.get('body', None)
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')
else:
state = 'pending'
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']
repository_owner = None
if sha is None:
# No special revision for this, so ignore it
continue
# If this is a pull request, send the status to the head repository
if 'pr_id' in props:
repository_name = props['head_reponame']
repository_owner = props['head_owner']
sha = props['head_sha']
elif 'repository_name' in props:
repository_name = props['repository_name']
else:
match = re.match(self.ssh_url_match, sourcestamp['repository'])
if match is not None:
repository_name = match.group("repo_name")
else:
log.msg(
"Could not send status, "
"build has no repository_name property for Gitea.")
continue
if repository_owner is None:
if 'owner' in props:
repository_owner = props['owner']
else:
match = re.match(self.ssh_url_match, sourcestamp['repository'])
if match is not None:
repository_owner = match.group("owner")
else:
log.msg(
"Could not send status, "
"build has no owner property for Gitea.")
continue
try:
target_url = build['url']
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}: {code} : {message}'.format(
state=state,
repo=sourcestamp['repository'], sha=sha,
code=res.code,
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
))

View File

@ -0,0 +1,33 @@
from __future__ import absolute_import
from __future__ import print_function
from twisted.internet import defer
from twisted.python import log
from buildbot.steps.source.git import Git
class Gitea(Git):
"""
Source step that knows how to handle merge requests from
the Gitea webhook
"""
@defer.inlineCallbacks
def _fetch(self, arg):
res = yield super(Gitea, self)._fetch(arg)
if self.build.hasProperty("pr_id") and self.repourl == self.build.getProperty("base_git_ssh_url", None):
remote = yield self._dovccmd(
['config', 'remote.pr_source.url'], collectStdout=True, abandonOnFailure=False)
if remote is None or remote.strip() is '':
yield self._dovccmd(
['remote', 'add', 'pr_source',
self.build.getProperty("head_git_ssh_url", None)])
else:
yield self._dovccmd(
['remote', 'set-url', 'pr_source',
self.build.getProperty("head_git_ssh_url", None)])
yield self._dovccmd(['fetch', 'pr_source'])
res = yield self._dovccmd(['merge', self.build.getProperty("head_sha", None)])
defer.returnValue(res)

View File

@ -0,0 +1,88 @@
import json
import mock
from buildbot.process.properties import Secret
from buildbot.test.util.config import ConfigErrorsMixin
from buildbot.test.util.misc import TestReactorMixin
from buildbot.test.util import www
from twisted.internet import defer
from twisted.trial import unittest
from buildbot.secrets.manager import SecretManager
from buildbot.test.fake.secrets import FakeSecretStorage
from buildbot_gitea.auth import GiteaAuth
try:
import requests
except ImportError:
requests = None
class FakeResponse:
def __init__(self, _json):
self.json = lambda: _json
self.content = json.dumps(_json)
def raise_for_status(self):
pass
class TestGiteaAuth(TestReactorMixin, www.WwwTestMixin, ConfigErrorsMixin,
unittest.TestCase):
def setUp(self):
self.setUpTestReactor()
if requests is None:
raise unittest.SkipTest("Need to install requests to test oauth2")
self.patch(requests, 'request', mock.Mock(spec=requests.request))
self.patch(requests, 'post', mock.Mock(spec=requests.post))
self.patch(requests, 'get', mock.Mock(spec=requests.get))
self.giteaAuth = GiteaAuth(
'https://gitea.test',
'client-id',
'client-secret')
self._master = master = self.make_master(
url='h:/a/b/', auth=self.giteaAuth)
self.giteaAuth.reconfigAuth(master, master.config)
self.giteaAuth_secret = GiteaAuth(
'https://gitea.test',
Secret("client-id"),
Secret("client-secret"))
self._master = master = self.make_master(
url='h:/a/b/', auth=self.giteaAuth_secret)
fake_storage_service = FakeSecretStorage()
fake_storage_service.reconfigService(
secretdict={
"client-id": "secretClientId",
"client-secret": "secretClientSecret"
})
secret_service = SecretManager()
secret_service.services = [fake_storage_service]
secret_service.setServiceParent(self._master)
self.giteaAuth_secret.reconfigAuth(master, master.config)
@defer.inlineCallbacks
def test_getGiteaLoginURL(self):
res = yield self.giteaAuth.getLoginURL('http://redir')
exp = ("https://gitea.test/login/oauth/authorize?client_id=client-id&"
"redirect_uri=h%3A%2Fa%2Fb%2Fauth%2Flogin&response_type=code&"
"state=redirect%3Dhttp%253A%252F%252Fredir")
self.assertEqual(res, exp)
res = yield self.giteaAuth.getLoginURL(None)
exp = ("https://gitea.test/login/oauth/authorize?client_id=client-id&"
"redirect_uri=h%3A%2Fa%2Fb%2Fauth%2Flogin&response_type=code")
self.assertEqual(res, exp)
@defer.inlineCallbacks
def test_getGiteaLoginURL_with_secret(self):
res = yield self.giteaAuth_secret.getLoginURL('http://redir')
exp = ("https://gitea.test/login/oauth/authorize?client_id=secretClientId&"
"redirect_uri=h%3A%2Fa%2Fb%2Fauth%2Flogin&response_type=code&"
"state=redirect%3Dhttp%253A%252F%252Fredir")
self.assertEqual(res, exp)
res = yield self.giteaAuth_secret.getLoginURL(None)
exp = ("https://gitea.test/login/oauth/authorize?client_id=secretClientId&"
"redirect_uri=h%3A%2Fa%2Fb%2Fauth%2Flogin&response_type=code")
self.assertEqual(res, exp)

View File

@ -0,0 +1,216 @@
# 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
from buildbot.test.util.misc import TestReactorMixin
class TestGiteaStatusPush(
unittest.TestCase,
ReporterTestMixin,
logging.LoggingMixin,
TestReactorMixin):
@defer.inlineCallbacks
def setUp(self):
self.setUpTestReactor()
self.setup_reporter_test()
# repository must be in the form http://gitea/<owner>/<project>
self.reporter_test_repo = u'http://gitea/buildbot/buildbot'
# 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.getService(
self.master, self,
"http://gitea", headers={'Authorization': 'token XXYYZZ'},
debug=None, verify=None)
self.sp = GiteaStatusPush("http://gitea/", Interpolate('XXYYZZ'))
yield self.sp.setServiceParent(self.master)
def tearDown(self):
return self.master.stopService()
def setupProps(self):
self.reporter_test_props['owner'] = "buildbot"
self.reporter_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.', 'context': '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.', 'context': '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.', 'context': 'buildbot/Builder0'})
build['complete'] = False
self.sp._got_event(('builds', 20, 'new'), build)
build['complete'] = True
self.sp._got_event(('builds', 20, 'finished'), build)
build['results'] = FAILURE
self.sp._got_event(('builds', 20, 'finished'), build)
@defer.inlineCallbacks
def test_pullrequest(self):
self.setupProps()
self.reporter_test_props["pr_id"] = 42
self.reporter_test_props["head_owner"] = 'foo'
self.reporter_test_props["head_reponame"] = 'bar'
build = yield self.setupBuildResults(SUCCESS)
# we make sure proper calls to txrequests have been made
self._http.expect(
'post',
'/api/v1/repos/foo/bar/statuses/d34db33fd43db33f',
json={'state': 'success',
'target_url': 'http://localhost:8080/#builders/79/builds/0',
'description': 'Build done.', 'context': 'buildbot/pull_request/Builder0'})
build['complete'] = True
self.sp._got_event(('builds', 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.', 'context': 'buildbot/Builder0'})
build['complete'] = False
self.sp._got_event(('builds', 20, 'new'), build)
@defer.inlineCallbacks
def test_sshurl_noprops(self):
self.reporter_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.', 'context': 'buildbot/Builder0'})
build['complete'] = False
self.sp._got_event(('builds', 20, 'new'), build)
@defer.inlineCallbacks
def test_noowner(self):
self.setUpLogging()
self.setupProps()
del self.reporter_test_props["owner"]
self.TEST_REPO = u''
build = yield self.setupBuildResults(SUCCESS)
build['complete'] = False
self.sp._got_event(('builds', 20, 'new'), 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.reporter_test_props["repository_name"]
self.TEST_REPO = u''
build = yield self.setupBuildResults(SUCCESS)
build['complete'] = False
self.sp._got_event(('builds', 20, 'new'), 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.insert_build_new()
# 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.', 'context': 'buildbot/Builder0'},
content_json={
"message": "sha1 not found: d34db33fd43db33f",
"url": "https://godoc.org/github.com/go-gitea/go-sdk/gitea"
},
code=500)
build['complete'] = False
self.sp._got_event(("builds", 20, "new"), build)
self.assertLogged(
"Could not send status \"pending\" for "
"http://gitea/buildbot/buildbot at d34db33fd43db33f:"
" 500 : sha1 not found: d34db33fd43db33f")
@defer.inlineCallbacks
def test_badchange(self):
self.setupProps()
self.setUpLogging()
build = yield self.insert_build_new()
# we make sure proper calls to txrequests have been made
self._http.expect(
'post',
'/api/v1/repos/buildbot/buildbot/statuses/d34db33fd43db33f',
json={
'state': 'pending',
'description': 'Build started.',
'target_url': 'http://localhost:8080/#builders/79/builds/0',
'context': 'buildbot/Builder0'
},
content_json={"message": "Not found"},
code=404,
)
build['complete'] = False
yield self.sp._got_event(("builds", 20, "new"), build)
self.assertLogged("Could not send status \"pending\" for"
" http://gitea/buildbot/buildbot at d34db33fd43db33f")
self.flushLoggedErrors(AssertionError)

View File

@ -0,0 +1,107 @@
# This file is part of Buildbot. Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members
from __future__ import absolute_import
from __future__ import print_function
from twisted.trial import unittest
from buildbot.process.results import SUCCESS
from buildbot_gitea.step_source import Gitea
from buildbot.test.fake.remotecommand import Expect
from buildbot.test.fake.remotecommand import ExpectShell
from buildbot.test.util import config
from buildbot.test.util import sourcesteps
from buildbot.test.util.misc import TestReactorMixin
class TestGitea(sourcesteps.SourceStepMixin, config.ConfigErrorsMixin, unittest.TestCase, TestReactorMixin):
stepClass = Gitea
def setUp(self):
self.setUpTestReactor()
self.sourceName = self.stepClass.__name__
return self.setUpSourceStep()
def setupStep(self, step, **kwargs):
step = sourcesteps.SourceStepMixin.setupStep(self, step, **kwargs)
step.build.properties.setProperty("pr_id", "1", "gitea pr id")
step.build.properties.setProperty("base_sha", "f6ad368298bd941e934a41f3babc827b2aa95a1d", "gitea source branch")
step.build.properties.setProperty("base_branch", "master", "gitea source branch")
step.build.properties.setProperty("base_git_ssh_url",
"git@gitea.example.com:base/awesome_project.git",
"gitea source git ssh url")
step.build.properties.setProperty("head_sha", "e4cd1224c622d46a8199c85c858485723115d2c8", "gitea target sha")
step.build.properties.setProperty("head_branch", "feature-branch", "gitea target branch")
step.build.properties.setProperty("head_git_ssh_url",
"git@gitea.example.com:target/awesome_project.git",
"gitea target git ssh url")
return step
def tearDown(self):
return self.tearDownSourceStep()
def test_with_merge_branch(self):
self.setupStep(
Gitea(repourl='git@gitea.example.com:base/awesome_project.git',
mode='full', method='clean'))
self.expectCommands(
ExpectShell(workdir='wkdir',
command=['git', '--version'])
+ ExpectShell.log('stdio',
stdout='git version 1.7.5')
+ 0,
Expect('stat', dict(file='wkdir/.buildbot-patched',
logEnviron=True))
+ 1,
Expect('listdir', {'dir': 'wkdir', 'logEnviron': True,
'timeout': 1200})
+ Expect.update('files', ['.git'])
+ 0,
ExpectShell(workdir='wkdir',
command=['git', 'clean', '-f', '-f', '-d'])
+ 0,
# here we always ignore revision, and fetch the merge branch
ExpectShell(workdir='wkdir',
command=['git', 'fetch', '-f', '-t',
'git@gitea.example.com:base/awesome_project.git', 'HEAD', '--progress'])
+ 0,
ExpectShell(workdir='wkdir',
command=['git', 'reset', '--hard', 'FETCH_HEAD', '--'])
+ 0,
ExpectShell(workdir='wkdir',
command=['git', 'config', 'remote.pr_source.url'])
+ 0,
ExpectShell(workdir='wkdir',
command=['git', 'remote', 'add', 'pr_source', 'git@gitea.example.com:target/awesome_project.git'])
+ 0,
ExpectShell(workdir='wkdir',
command=['git', 'fetch', 'pr_source'])
+ 0,
ExpectShell(workdir='wkdir',
command=['git', 'merge', 'e4cd1224c622d46a8199c85c858485723115d2c8'])
+ 0,
ExpectShell(workdir='wkdir',
command=['git', 'rev-parse', 'HEAD'])
+ ExpectShell.log('stdio',
stdout='e4cd1224c622d46a8199c85c858485723115d2c8')
+ 0,
)
self.expectOutcome(result=SUCCESS)
self.expectProperty(
'got_revision', 'e4cd1224c622d46a8199c85c858485723115d2c8', 'Gitea')
return self.runStep()

View File

@ -1171,112 +1171,6 @@ giteaJsonPullRequestFork = rb"""
""" """
giteaJsonPushEmptyFiles = rb"""
{
"secret": "pass",
"ref": "refs/heads/develop",
"before": "2437bd7c6b0af7b8da570973c02f0cca07ec787d",
"after": "2437bd7c6b0af7b8da570973c02f0cca07ec787d",
"compare_url": "",
"commits": [
{
"id": "2437bd7c6b0af7b8da570973c02f0cca07ec787d",
"message": "snip",
"url": "snip/commit/2437bd7c6b0af7b8da570973c02f0cca07ec787d",
"author": {
"name": "snip",
"email": "snip",
"username": ""
},
"committer": {
"name": "snip",
"email": "snip",
"username": ""
},
"verification": null,
"timestamp": "0001-01-01T00:00:00Z",
"added": null,
"removed": null,
"modified": null
}
],
"head_commit": {
"id": "2437bd7c6b0af7b8da570973c02f0cca07ec787d",
"message": "snip",
"url": "snip/commit/2437bd7c6b0af7b8da570973c02f0cca07ec787d",
"author": {
"name": "snip",
"email": "snip",
"username": ""
},
"committer": {
"name": "snip",
"email": "snip",
"username": ""
},
"verification": null,
"timestamp": "0001-01-01T00:00:00Z",
"added": null,
"removed": null,
"modified": null
},
"repository": {
"id": "snip",
"owner": {"id":"snip","login":"snip","full_name":"snip","email":"snip","avatar_url":"snip","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2019-07-04T02:15:26+02:00","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"snip"},
"name": "snip",
"full_name": "snip",
"description": "snip",
"empty": false,
"private": true,
"fork": false,
"template": false,
"parent": null,
"mirror": false,
"size": 19106,
"html_url": "snip",
"ssh_url": "git@snip.git",
"clone_url": "snip.git",
"original_url": "",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 5,
"open_issues_count": 33,
"open_pr_counter": 1,
"release_counter": 0,
"default_branch": "develop",
"archived": false,
"created_at": "2020-08-25T09:34:29+02:00",
"updated_at": "2022-01-19T15:55:11+01:00",
"permissions": {
"admin": false,
"push": false,
"pull": false
},
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": false,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": true,
"has_projects": false,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": true,
"allow_rebase": true,
"allow_rebase_explicit": false,
"allow_squash_merge": true,
"default_merge_style": "merge",
"avatar_url": "",
"internal": false,
"mirror_interval": ""
},
"pusher": {"id":"snip","login":"snip","full_name":"snip","email":"snip","avatar_url":"snip","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2021-01-22T02:15:29+01:00","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"snip"},
"sender": {"id":"snip","login":"snip","full_name":"snip","email":"snip","avatar_url":"snip","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2021-01-22T02:15:29+01:00","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"snip"}
}
"""
class TestChangeHookGiteaPush(unittest.TestCase, TestReactorMixin): class TestChangeHookGiteaPush(unittest.TestCase, TestReactorMixin):
def setUp(self): def setUp(self):
self.setUpTestReactor() self.setUpTestReactor()
@ -1337,23 +1231,6 @@ class TestChangeHookGiteaPush(unittest.TestCase, TestReactorMixin):
"https://git.example.com/Test/test/commit/ea07c3148db428876add8b312256239275c395fb") "https://git.example.com/Test/test/commit/ea07c3148db428876add8b312256239275c395fb")
self.assertEqual(change["files"], ["testfile2", "testfile1", "testfile3"]) self.assertEqual(change["files"], ["testfile2", "testfile1", "testfile3"])
def checkNoFileChanges(self, codebase=None):
self.assertEqual(len(self.changeHook.master.data.updates.changesAdded), 1)
change = self.changeHook.master.data.updates.changesAdded[0]
self.assertEqual(change['repository'], 'git@snip.git')
self.assertEqual(
change["author"], "snip <snip>")
self.assertEqual(
change["revision"], '2437bd7c6b0af7b8da570973c02f0cca07ec787d')
self.assertEqual(
change["comments"], "snip")
self.assertEqual(change["branch"], "develop")
self.assertEqual(change[
"revlink"],
"snip/commit/2437bd7c6b0af7b8da570973c02f0cca07ec787d")
self.assertEqual(change["files"], [])
def checkChangesFromPullRequest(self, codebase=None): def checkChangesFromPullRequest(self, codebase=None):
self.assertEqual(len(self.changeHook.master.data.updates.changesAdded), 1) self.assertEqual(len(self.changeHook.master.data.updates.changesAdded), 1)
change = self.changeHook.master.data.updates.changesAdded[0] change = self.changeHook.master.data.updates.changesAdded[0]
@ -1430,15 +1307,6 @@ class TestChangeHookGiteaPush(unittest.TestCase, TestReactorMixin):
res = yield self.request.test_render(self.changeHook) res = yield self.request.test_render(self.changeHook)
self.checkFileChanges(res) self.checkFileChanges(res)
@defer.inlineCallbacks
def testNoChangedFiles(self):
self.request = FakeRequest(content=giteaJsonPushEmptyFiles)
self.request.uri = b'/change_hook/gitea'
self.request.method = b'POST'
self.request.received_headers[_HEADER_EVENT_TYPE] = b"push"
res = yield self.request.test_render(self.changeHook)
self.checkNoFileChanges(res)
@defer.inlineCallbacks @defer.inlineCallbacks
def testPullRequestEvent(self): def testPullRequestEvent(self):
self.request = FakeRequest(content=giteaJsonPullRequestPayload) self.request = FakeRequest(content=giteaJsonPullRequestPayload)

View File

@ -1,4 +1,3 @@
import base64
import json import json
import re import re
import hmac import hmac
@ -12,14 +11,13 @@ from twisted.python import log
from dateutil.parser import parse as dateparse from dateutil.parser import parse as dateparse
_HEADER_USER_AGENT = 'User-Agent' _HEADER_EVENT_TYPE = 'X-Gitea-Event'
_HEADER_SIGNATURE = 'X-TX-Signature' _HEADER_SIGNATURE = 'X-Gitea-Signature'
_EVENT_KEY = 'event'
class TransifexHandler(BaseHookHandler): class GiteaHandler(BaseHookHandler):
def process_translation_completed(self, payload, event_type, codebase): def process_push(self, payload, event_type, codebase):
refname = payload["ref"] refname = payload["ref"]
changes = [] changes = []
@ -43,7 +41,7 @@ class TransifexHandler(BaseHookHandler):
for commit in commits: for commit in commits:
files = [] files = []
for kind in ('added', 'modified', 'removed'): for kind in ('added', 'modified', 'removed'):
files.extend(commit.get(kind, []) or []) files.extend(commit.get(kind, []))
timestamp = dateparse(commit['timestamp']) timestamp = dateparse(commit['timestamp'])
change = { change = {
'author': '{} <{}>'.format(commit['author']['name'], 'author': '{} <{}>'.format(commit['author']['name'],
@ -68,7 +66,7 @@ class TransifexHandler(BaseHookHandler):
changes.insert(0, change) changes.insert(0, change)
return changes return changes
def process_review_compoleted(self, payload, event_type, codebase): def process_pull_request(self, payload, event_type, codebase):
action = payload['action'] action = payload['action']
# Only handle potential new stuff, ignore close/. # Only handle potential new stuff, ignore close/.
@ -125,14 +123,6 @@ class TransifexHandler(BaseHookHandler):
change['codebase'] = codebase change['codebase'] = codebase
return [change] return [change]
def _transform_variables(payload):
retval = {
project: payload.get('project'),
repository = [payload.get('resource')],
branch = payload.get('language')
}
return retval
@defer.inlineCallbacks @defer.inlineCallbacks
def getChanges(self, request): def getChanges(self, request):
secret = None secret = None
@ -145,7 +135,6 @@ class TransifexHandler(BaseHookHandler):
except Exception as exception: except Exception as exception:
raise ValueError('Error loading JSON: ' + str(exception)) raise ValueError('Error loading JSON: ' + str(exception))
if secret is not None: if secret is not None:
p = Properties() p = Properties()
p.master = self.master p.master = self.master
@ -156,38 +145,39 @@ class TransifexHandler(BaseHookHandler):
digestmod=hashlib.sha256) digestmod=hashlib.sha256)
header_signature = bytes2unicode( header_signature = bytes2unicode(
request.getHeader(_HEADER_SIGNATURE)) request.getHeader(_HEADER_SIGNATURE))
if signature.hexdigest() != header_signature:
http_verb = 'POST'
http_url_path = request.headers('X-TX-Url')
http_gmt_date = request.headers('Date')
content_md5 = hashlib.md5(content).hexdigest()
msg = b'\n'.join([
http_verb, http_url_path, http_gmt_date, content_md5
])
tx_signature = base64.b64encode(
hmac.new(
key=rendered_secret,
msg=msg,
digestmod=hashlib.sha256
).digest()
)
if tx_signature() != header_signature:
raise ValueError('Invalid secret') raise ValueError('Invalid secret')
event_type = bytes2unicode(payload.get(_EVENT_KEY), "None") event_type = bytes2unicode(request.getHeader(_HEADER_EVENT_TYPE))
log.msg("Received event '{}' from transifex".format(event_type)) log.msg("Received event '{}' from gitea".format(event_type))
codebase = "" codebases = request.args.get('codebase', [None])
codebase = bytes2unicode(codebases[0])
changes = [] changes = []
handler_function = getattr(self, 'process_{}'.format(event_type), None) handler_function = getattr(self, 'process_{}'.format(event_type), None)
if not handler_function: if not handler_function:
log.msg("Ignoring transifex event '{}'".format(event_type)) log.msg("Ignoring gitea event '{}'".format(event_type))
else: else:
changes = handler_function(payload, event_type, codebase) changes = handler_function(payload, event_type, codebase)
return (changes, 'transifex') return (changes, 'git')
class GiteaHandlerPlugin(BaseHookHandler):
def __init__(self, master, options):
if not options:
options = {}
super().__init__(master, options)
handler_class = options.get('class', GiteaHandler)
if 'class' in options:
del options['class']
self.handler = handler_class(master, options)
def getChanges(self, request):
return self.handler.getChanges(request)
# Plugin name # Plugin name
transifex = TransifexHandler gitea = GiteaHandlerPlugin

View File

@ -6,7 +6,7 @@ from setuptools import setup
with open("README.md", "r") as fh: with open("README.md", "r") as fh:
long_description = fh.read() long_description = fh.read()
VERSION = "1.7.2" VERSION = "1.6.1"
setup(name='buildbot-gitea', setup(name='buildbot-gitea',
version=VERSION, version=VERSION,