builder.braak.pro/config/gitea/reporter.py
2024-11-19 21:41:39 +01:00

279 lines
9.7 KiB
Python

# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2018 LAB132
# SPDX-FileCopyrightText: 2013-2024 Blender Authors
# <pep8 compliant>
# 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.reporters.generators.build import BuildStartEndStatusGenerator
from buildbot.reporters.message import MessageFormatterRenderable
import re
# This name has a number in it to trick buildbot into reloading it on without
# restart. Needs to be incremented every time this file is changed. Is there
# a better solution?
class GiteaStatusService11(http.ReporterBase):
name = "GiteaStatusService11"
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
url = "/api/v1/repos/{owner}/{repository}/statuses/{sha}".format(
owner=project_owner, repository=repo_name, sha=sha
)
log.msg(f"Sending status to {url}: {payload}")
return self._http.post(url, json=payload)
@defer.inlineCallbacks
def sendMessage(self, reports):
yield self._send_impl(reports)
@defer.inlineCallbacks
def _send_status(
self, build, repository_owner, repository_name, sha, state, context, description
):
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=repository_name, sha=sha, code=res.code, message=message
)
)
elif self.verbose:
log.msg(
'Status "{state}" sent for '
"{repo} at {sha}.".format(state=state, repo=repository_name, sha=sha)
)
except Exception as e:
log.err(
e,
'Failed to send status "{state}" for '
"{repo} at {sha}".format(state=state, repo=repository_name, sha=sha),
)
@defer.inlineCallbacks
def _send_impl(self, reports):
for report in reports:
try:
builds = report["builds"]
except KeyError:
continue
for build in builds:
builder_name = build["builder"]["name"]
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"]
# BLENDER: some hardcoded logic for now.
if (
"-code-daily-" in builder_name
or "-code-patch-" in builder_name
or "-code-experimental-" in builder_name
):
repository_owner = "blender"
repository_name = "blender"
elif "-doc-manual-" in builder_name:
repository_owner = "blender"
repository_name = "blender-manual"
elif "-doc-developer" in builder_name:
repository_owner = "blender"
repository_name = "blender-developer-docs"
else:
continue
# Source change from Git poller.
for sourcestamp in sourcestamps:
sha = sourcestamp["revision"]
if sha not in {None, "", "HEAD"}:
self._send_status(
build,
repository_owner,
repository_name,
sha,
state,
context,
description,
)
continue
# Revision specified by get-revision step.
if "revision" in props:
sha = props["revision"]
if sha not in {None, "", "HEAD"}:
self._send_status(
build,
repository_owner,
repository_name,
sha,
state,
context,
description,
)
# Revision from blender-bot, so we can send a status before
# the get-revision step runs.
if "pull_revision" in props:
sha = props["pull_revision"]
if sha not in {None, "", "HEAD"}:
self._send_status(
build,
repository_owner,
repository_name,
sha,
state,
context,
description,
)
continue