From f8e3cb77ed7fdcdeec9504576df9bec6103dcd28 Mon Sep 17 00:00:00 2001 From: Thomas Bachner Date: Mon, 22 Jun 2026 17:14:09 +0200 Subject: [PATCH] Added API-Token handling for Authentication to ReBenchDB. --- docs/config.md | 5 +++++ rebench/configurator.py | 3 ++- rebench/rebench-schema.yml | 3 +++ rebench/rebenchdb.py | 27 ++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/docs/config.md b/docs/config.md index d67a7568..049f658f 100644 --- a/docs/config.md +++ b/docs/config.md @@ -460,8 +460,13 @@ reporting: repo_url: https://example.org/project.git project_name: Some Project that Wants Good Performance record_all: true + api_token: your-api-token-here ``` +The `api_token` is obtained from the ReBenchDB admin UI by clicking +"Generate My API Token". The full token is shown only once, at generation +time — copy it immediately. + --- ## Machines diff --git a/rebench/configurator.py b/rebench/configurator.py index aa269d8e..2df9a636 100644 --- a/rebench/configurator.py +++ b/rebench/configurator.py @@ -291,7 +291,8 @@ def get_rebench_db_connector(self): self._rebench_db_connector = ReBenchDB( self.rebench_db['db_url'], self.rebench_db['project_name'], - self.options.experiment_name, self.ui) + self.options.experiment_name, self.ui, + self.rebench_db.get('api_token')) return self._rebench_db_connector def _process_cli_options(self): diff --git a/rebench/rebench-schema.yml b/rebench/rebench-schema.yml index ad1789d6..1c09b744 100644 --- a/rebench/rebench-schema.yml +++ b/rebench/rebench-schema.yml @@ -94,6 +94,9 @@ schema;reporting_type: record_all: type: bool desc: All experiments should be stored in the ReBenchDB + api_token: + type: str + desc: API token for authenticating with ReBenchDB. codespeed: type: map desc: Send results to Codespeed for continuous performance tracking. diff --git a/rebench/rebenchdb.py b/rebench/rebenchdb.py index c0a32f01..215da32c 100644 --- a/rebench/rebenchdb.py +++ b/rebench/rebenchdb.py @@ -2,6 +2,7 @@ from time import sleep from http.client import HTTPException +from urllib.error import HTTPError from urllib.request import urlopen, Request as HttpRequest from .output import UIError @@ -27,7 +28,7 @@ def get_current_time(): class ReBenchDB(object): - def __init__(self, server_base_url, project_name, experiment_name, ui): + def __init__(self, server_base_url, project_name, experiment_name, ui, api_token=None): self.ui = ui if not server_base_url: @@ -45,6 +46,7 @@ def __init__(self, server_base_url, project_name, experiment_name, ui): self._server_base_url = server_base_url self._project_name = project_name self._experiment_name = experiment_name + self._api_token = api_token self._api_v2 = None def is_api_v2(self): @@ -109,6 +111,8 @@ def convert_data_to_json(self, data): def _send_to_rebench_db(self, payload_data, operation): payload_data["projectName"] = self._project_name payload_data["experimentName"] = self._experiment_name + if self._api_token: + payload_data["token"] = self._api_token url = self._server_base_url + operation payload = self.convert_data_to_json(payload_data) @@ -131,6 +135,27 @@ def _send_with_retries(self, payload_bytes, url): self.ui.error("{ind}Error: Reporting to ReBenchDB failed.\n" + "{ind}{ind}" + str(te) + "\n") return False, None + except HTTPError as error: + body = error.read().decode("utf-8", errors="replace") + if error.code == 401 and "Authorization required" in body: + message = ( + "ReBenchDB requires an API token, but none is configured. " + "Set reporting.rebenchdb.api_token in your configuration file. " + "Server response: " + body) + elif error.code == 401: + message = "ReBenchDB rejected the configured API token. Server response: " + body + elif error.code == 403: + message = ( + "ReBenchDB denied write access for this token. Check that project_name " + "matches the server's project name exactly. " + "Server response: " + body) + else: + message = ( + "ReBenchDB request failed with status " + str(error.code) + + ". Server response: " + body) + self.ui.error("{ind}Error: Reporting to ReBenchDB failed.\n" + + "{ind}{ind}" + message + "\n") + return False, None except (IOError, HTTPException) as error: # pylint: disable-next=no-member is_client_error = hasattr(error, "status") and 400 <= error.status < 500