This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git
The following commit(s) were added to refs/heads/main by this push:
new 41b3ae8 Add an API endpoint to create a new release
41b3ae8 is described below
commit 41b3ae81f52caf3a3610f9e930b1c6d1d665d725
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Jul 3 21:50:58 2025 +0100
Add an API endpoint to create a new release
---
atr/blueprints/api/api.py | 42 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index e5f869c..f64525d 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -20,6 +20,7 @@ import datetime
import hashlib
from collections.abc import Mapping
+import asfquart.base as base
import quart
import quart_schema
import sqlalchemy
@@ -31,6 +32,8 @@ import atr.blueprints.api as api
import atr.db as db
import atr.db.models as models
import atr.jwtoken as jwtoken
+import atr.routes as routes
+import atr.routes.start as start
import atr.schema as schema
# FIXME: we need to return the dumped model instead of the actual pydantic
class
@@ -60,6 +63,11 @@ class PATJWTRequest(schema.Strict):
pat: str
+class ReleaseCreateRequest(schema.Strict):
+ project_name: str
+ version: str
+
+
# We implicitly have /api/openapi.json
@@ -211,6 +219,32 @@ async def releases(query_args: Releases) -> quart.Response:
return quart.jsonify(result)
[email protected]("/releases/create", methods=["POST"])
[email protected]
+@quart_schema.security_scheme([{"BearerAuth": []}])
+@quart_schema.validate_response(models.Release, 201)
+async def releases_create() -> tuple[Mapping, int]:
+ """Create a new release draft for a project via POSTed JSON."""
+
+ payload = await quart.request.get_json(force=True, silent=False)
+ if not isinstance(payload, dict):
+ raise exceptions.BadRequest("Invalid JSON")
+
+ request_data = ReleaseCreateRequest.model_validate(payload)
+ asf_uid = _jwt_asf_uid()
+
+ try:
+ release, _project = await start.create_release_draft(
+ project_name=request_data.project_name,
+ version=request_data.version,
+ asf_uid=asf_uid,
+ )
+ except routes.FlashError as exc:
+ raise exceptions.BadRequest(str(exc))
+
+ return release.model_dump(), 201
+
+
@api.BLUEPRINT.route("/releases/<project>/<version>")
@quart_schema.validate_response(models.Release, 200)
async def releases_project_version(project: str, version: str) ->
tuple[Mapping, int]:
@@ -303,6 +337,14 @@ async def _get_pat(data: db.Session, uid: str, token_hash:
str) -> models.Person
)
+def _jwt_asf_uid() -> str:
+ claims = getattr(quart.g, "jwt_claims", {})
+ asf_uid = claims.get("sub")
+ if not isinstance(asf_uid, str):
+ raise base.ASFQuartException("Invalid token subject", errorcode=401)
+ return asf_uid
+
+
def _pagination_args_validate(query_args: Pagination) -> None:
# Users could request any amount using limit=N with arbitrarily high N
# We therefore limit the maximum limit to 1000
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]