294 lines
9.9 KiB
Python
294 lines
9.9 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
|