buildbot-transifex/buildbot_transifex/webhook.py

265 lines
11 KiB
Python
Raw Normal View History

2022-12-28 09:04:44 +01:00
import base64
import json
import re
import hmac
import hashlib
from buildbot.process.properties import Properties
from buildbot.util import bytes2unicode, unicode2bytes
from buildbot.www.hooks.base import BaseHookHandler
from twisted.internet import defer
from twisted.python import log
2018-09-04 12:58:05 +02:00
from dateutil.parser import parse as dateparse
2022-12-28 09:04:44 +01:00
_HEADER_USER_AGENT = 'User-Agent'
_HEADER_SIGNATURE = 'X-TX-Signature'
_EVENT_KEY = 'event'
2022-12-28 09:04:44 +01:00
class TransifexHandler(BaseHookHandler):
2023-01-02 16:00:09 +01:00
# def verifyGitHubSignature (environ, payload_body):
# signature = 'sha1=' + hmac.new(gitHubSecret, payload_body, sha1).hexdigest()
# try:
# if signature != environ.get('X-TX-Signature'):
# return False
# return True
# except:
# return False
# def verifyTransifexSignature (environ, payload_body):
# signature = b64encode(hmac.new(transifexSecret, payload_body, sha1).digest())
# try:
# debug(signature)
# if signature != environ.get('HTTP_X_TX_SIGNATURE'):
# return False
# return True
# except:
# return False
# def returnMessage(status = False, message = "Unimplemented"):
# output = json.dumps({"status": "OK" if status else "FAIL", "message": message})
# return [output, [('Content-type', 'text/plain'),
# ('Content-Length', str(len(output)))
# ]]
# def application(environ, start_response):
# status = '200 OK'
# output = ''
# lockWait()
# length = int(environ.get('CONTENT_LENGTH', '0'))
# body = environ['wsgi.input'].read(length)
# if "Transifex" in environ.get("HTTP_USER_AGENT"):
# # debug(environ)
# # debug(body)
# if not verifyTransifexSignature(environ, body):
# debug ("Verify Transifex Signature fail, but fuck them")
# else:
# debug ("Verify Transifex Signature ok")
# # output, responseHeaders = returnMessage(False, "Checksum bad")
# # start_response(status, responseHeaders)
# # unlock()
# # return [output]
# try:
# # debug(body)
# payload = parse_qs(body)
# # debug(payload)
# if 'pybitmessage' in payload['project'] and 'pybitmessage' in payload['resource']:
# if 'translated' in payload and '100' in payload['translated']:
# ts = int(time.time())
# updateLocalTranslationDestination(ts, payload['language'][0].lower())
# downloadTranslatedLanguage(ts, payload['language'][0])
# response = commitTranslatedLanguage(ts, payload['language'][0].lower())
# if response.ok:
# output, responseHeaders = returnMessage(True, "Processed.")
# else:
# output, responseHeaders = returnMessage(False, "Error: %i." % (response.status_code))
# else:
# output, responseHeaders = returnMessage(False, "Nothing to do")
# else:
# output, responseHeaders = returnMessage(False, "Nothing to do")
# except:
# output, responseHeaders = returnMessage(True, "Not processing")
# else:
# debug("Unknown command %s" % (environ.get("HTTP_X_GITHUB_EVENT")))
# output, responseHeaders = returnMessage(True, "Unknown command, ignoring")
def __init__(self, transifex_dict=None):
super(TransifexHandler, self).__init__(*args, **kwargs)
2022-12-28 09:04:44 +01:00
def process_translation_completed(self, payload, event_type, codebase):
refname = payload["ref"]
changes = []
2023-01-02 16:00:09 +01:00
# We only care about regular heads or tags
match = re.match(r"^refs/(heads|tags)/(.+)$", refname)
2023-01-02 16:00:09 +01:00
if event_type == 'translation_completed':
pass
if not match:
log.msg("Ignoring refname '{}': Not a branch or tag".format(refname))
return changes
branch = match.group(2)
2018-09-04 15:04:29 +02:00
repository = payload['repository']
repo_url = repository['ssh_url']
project = repository['full_name']
commits = payload['commits']
if isinstance(self.options, dict) and self.options.get('onlyIncludePushCommit', False):
commits = commits[:1]
for commit in commits:
files = []
for kind in ('added', 'modified', 'removed'):
files.extend(commit.get(kind, []) or [])
2018-09-04 15:04:29 +02:00
timestamp = dateparse(commit['timestamp'])
change = {
2018-09-04 15:10:46 +02:00
'author': '{} <{}>'.format(commit['author']['name'],
commit['author']['email']),
'files': files,
'comments': commit['message'],
'revision': commit['id'],
'when_timestamp': timestamp,
'branch': branch,
'revlink': commit['url'],
'repository': repo_url,
'project': project,
'category': event_type,
'properties': {
'event': event_type,
2018-09-27 14:46:35 +02:00
'repository_name': repository['name'],
'owner': repository["owner"]["username"]
},
}
if codebase is not None:
change['codebase'] = codebase
changes.insert(0, change)
return changes
2023-01-02 16:00:09 +01:00
def process_review_completed(self, payload, event_type, codebase):
2018-09-04 15:04:29 +02:00
action = payload['action']
2023-01-02 16:00:09 +01:00
if event_type == 'review_completed':
pass
2018-09-04 15:04:29 +02:00
# Only handle potential new stuff, ignore close/.
# Merge itself is handled by the regular branch push message
if action not in ['opened', 'synchronized', 'edited', 'reopened']:
2023-01-02 16:00:09 +01:00
log.msg("Transifex Pull Request event '{}' ignored".format(action))
return []
2023-01-02 16:00:09 +01:00
# pull_request = payload['pull_request']
# if not pull_request['mergeable']:
# log.msg("Transifex Pull Request ignored because it is not mergeable.")
# return []
# if pull_request['merged']:
# log.msg("Transifex Pull Request ignored because it is already merged.")
# return []
# timestamp = dateparse(pull_request['updated_at'])
# base = pull_request['base']
# head = pull_request['head']
repository = payload['repository']
2018-09-04 15:04:29 +02:00
change = {
2018-09-04 15:10:46 +02:00
'author': '{} <{}>'.format(pull_request['user']['full_name'],
pull_request['user']['email']),
2023-01-02 16:00:09 +01:00
# 'comments': 'PR#{}: {}\n\n{}'.format(
# pull_request['number'],
# pull_request['title'],
# pull_request['body']),
'revision': base['sha'],
2018-09-04 15:04:29 +02:00
'when_timestamp': timestamp,
'branch': base['ref'],
2023-01-02 16:00:09 +01:00
# 'revlink': pull_request['html_url'],
'repository': base['repo']['ssh_url'],
2018-09-04 15:04:29 +02:00
'project': repository['full_name'],
'category': event_type,
'properties': {
'event': event_type,
'base_branch': base['ref'],
'base_sha': base['sha'],
'base_repo_id': base['repo_id'],
'base_repository': base['repo']['clone_url'],
'base_git_ssh_url': base['repo']['ssh_url'],
2018-09-04 15:04:29 +02:00
'head_branch': head['ref'],
'head_sha': head['sha'],
'head_repo_id': head['repo_id'],
'head_repository': head['repo']['clone_url'],
'head_git_ssh_url': head['repo']['ssh_url'],
'head_owner': head['repo']['owner']['username'],
'head_reponame': head['repo']['name'],
2023-01-02 16:00:09 +01:00
# 'pr_id': pull_request['id'],
# 'pr_number': pull_request['number'],
2018-09-27 14:46:35 +02:00
'repository_name': repository['name'],
'owner': repository["owner"]["username"],
2018-09-04 15:04:29 +02:00
},
}
if codebase is not None:
change['codebase'] = codebase
return [change]
2022-12-28 09:04:44 +01:00
def _transform_variables(payload):
retval = {
project: payload.get('project'),
repository = [payload.get('resource')],
branch = payload.get('language')
}
return retval
@defer.inlineCallbacks
def getChanges(self, request):
secret = None
if isinstance(self.options, dict):
2018-09-04 15:04:29 +02:00
secret = self.options.get('secret')
try:
content = request.content.read()
content_text = bytes2unicode(content)
payload = json.loads(content_text)
except Exception as exception:
raise ValueError('Error loading JSON: ' + str(exception))
if secret is not None:
p = Properties()
p.master = self.master
rendered_secret = yield p.render(secret)
signature = hmac.new(
unicode2bytes(rendered_secret),
unicode2bytes(content_text.strip()),
digestmod=hashlib.sha256)
header_signature = bytes2unicode(
request.getHeader(_HEADER_SIGNATURE))
2022-12-28 09:04:44 +01:00
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')
2022-12-28 09:04:44 +01:00
event_type = bytes2unicode(payload.get(_EVENT_KEY), "None")
log.msg("Received event '{}' from transifex".format(event_type))
2022-12-28 09:04:44 +01:00
codebase = ""
2018-09-04 15:04:29 +02:00
changes = []
handler_function = getattr(self, 'process_{}'.format(event_type), None)
if not handler_function:
2022-12-28 09:04:44 +01:00
log.msg("Ignoring transifex event '{}'".format(event_type))
else:
changes = handler_function(payload, event_type, codebase)
2018-09-04 15:04:29 +02:00
2022-12-28 09:04:44 +01:00
return (changes, 'transifex')
2023-01-02 16:00:09 +01:00
transifex = TransifexHandler(transifex_dict)