# SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2018 LAB132 # SPDX-FileCopyrightText: 2013-2024 Blender Authors # # 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[\w_\-\.]+)/(?P[\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