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 86acb73  Add an endpoint to resolve a vote using a GitHub OIDC JWT
86acb73 is described below

commit 86acb73ad7cb3ccbc79ce47de1abbd1fcf2107e5
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Sep 4 16:01:27 2025 +0100

    Add an endpoint to resolve a vote using a GitHub OIDC JWT
---
 atr/blueprints/api/api.py | 57 ++++++++++++++++++++++++++++++++++++++---------
 atr/models/api.py         | 13 +++++++++++
 2 files changed, 59 insertions(+), 11 deletions(-)

diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index b36c148..7537965 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -36,7 +36,6 @@ import atr.config as config
 import atr.db as db
 import atr.db.interaction as interaction
 import atr.jwtoken as jwtoken
-import atr.log as log
 import atr.models as models
 import atr.models.sql as sql
 import atr.revision as revision
@@ -260,14 +259,50 @@ async def committees_list() -> DictResponse:
     ).model_dump(), 200
 
 
[email protected]("/github/vote/resolve", methods=["POST"])
+@quart_schema.validate_request(models.api.GithubVoteResolveArgs)
+async def github_vote_resolve(data: models.api.GithubVoteResolveArgs) -> 
DictResponse:
+    """
+    Resolve a vote with a corroborating GitHub OIDC JWT.
+    """
+    _payload, asf_uid, project = await interaction.github_trusted_jwt(data.jwt)
+    if project.committee is None:
+        raise exceptions.NotFound("Project has no committee")
+    # WARNING: This is subtly different from the /vote/resolve code
+    async with db.session() as db_data:
+        release_name = sql.release_name(project.name, data.version)
+        release = await db_data.release(name=release_name, _project=True, 
_committee=True).demand(exceptions.NotFound())
+        if release.project.committee is None:
+            raise exceptions.NotFound("Project has no committee")
+        if release.project.committee.name != project.committee.name:
+            raise exceptions.BadRequest("Release project committee does not 
match the OIDC project committee")
+        _committee_member_or_admin(release.project.committee, asf_uid)
+
+        release = await db_data.merge(release)
+        match data.resolution:
+            case "passed":
+                release.phase = sql.ReleasePhase.RELEASE_PREVIEW
+                description = "Create a preview revision from the last 
candidate draft"
+                async with revision.create_and_manage(
+                    project.name, release.version, asf_uid, 
description=description
+                ) as _creating:
+                    pass
+            case "failed":
+                release.phase = sql.ReleasePhase.RELEASE_CANDIDATE_DRAFT
+        await db_data.commit()
+
+    return models.api.GithubVoteResolveResults(
+        endpoint="/github/vote/resolve",
+        success=True,
+    ).model_dump(), 200
+
+
 @api.BLUEPRINT.route("/github/ssh/register", methods=["POST"])
 @quart_schema.validate_request(models.api.GithubSshRegisterArgs)
 async def github_ssh_register(data: models.api.GithubSshRegisterArgs) -> 
DictResponse:
     """
     Register an SSH key sent with a corroborating GitHub OIDC JWT.
     """
-    log.info(f"SSH key: {data.ssh_key}")
-
     payload, asf_uid, project = await interaction.github_trusted_jwt(data.jwt)
     async with 
storage.write_as_committee_member(util.unwrap(project.committee).name, asf_uid) 
as wacm:
         fingerprint, expires = await wacm.ssh.add_workflow_key(
@@ -1158,14 +1193,14 @@ def _committee_member_or_admin(committee: 
sql.Committee, asf_uid: str) -> None:
         raise exceptions.Forbidden("You do not have permission to perform this 
action")
 
 
[email protected]_function
-async def _get_pat(data: db.Session, uid: str, token_hash: str) -> 
sql.PersonalAccessToken | None:
-    return await data.query_one_or_none(
-        sqlmodel.select(sql.PersonalAccessToken).where(
-            sql.PersonalAccessToken.asfuid == uid,
-            sql.PersonalAccessToken.token_hash == token_hash,
-        )
-    )
+# @db.session_function
+# async def _get_pat(data: db.Session, uid: str, token_hash: str) -> 
sql.PersonalAccessToken | None:
+#     return await data.query_one_or_none(
+#         sqlmodel.select(sql.PersonalAccessToken).where(
+#             sql.PersonalAccessToken.asfuid == uid,
+#             sql.PersonalAccessToken.token_hash == token_hash,
+#         )
+#     )
 
 
 def _jwt_asf_uid() -> str:
diff --git a/atr/models/api.py b/atr/models/api.py
index 74f0095..8c240a2 100644
--- a/atr/models/api.py
+++ b/atr/models/api.py
@@ -85,6 +85,17 @@ class GithubSshRegisterResults(schema.Strict):
     expires: int = schema.Field(..., **example(1713547200))
 
 
+class GithubVoteResolveArgs(schema.Strict):
+    jwt: str = schema.Field(..., 
**example("eyJhbGciOiJIUzI1[...]mMjLiuyu5CSpyHI="))
+    version: str = schema.Field(..., **example("0.0.1"))
+    resolution: Literal["passed", "failed"] = schema.Field(..., 
**example("passed"))
+
+
+class GithubVoteResolveResults(schema.Strict):
+    endpoint: Literal["/github/vote/resolve"] = schema.Field(alias="endpoint")
+    success: Literal[True] = schema.Field(..., **example(True))
+
+
 class IgnoreAddArgs(schema.Strict):
     committee_name: str = schema.Field(..., **example("example"))
     release_glob: str | None = schema.Field(default=None, 
**example("example-0.0.*"))
@@ -435,6 +446,7 @@ Results = Annotated[
     | CommitteeProjectsResults
     | CommitteesListResults
     | GithubSshRegisterResults
+    | GithubVoteResolveResults
     | IgnoreAddResults
     | IgnoreDeleteResults
     | IgnoreListResults
@@ -488,6 +500,7 @@ validate_committee_keys = validator(CommitteeKeysResults)
 validate_committee_projects = validator(CommitteeProjectsResults)
 validate_committees_list = validator(CommitteesListResults)
 validate_github_ssh_register = validator(GithubSshRegisterResults)
+validate_github_vote_resolve = validator(GithubVoteResolveResults)
 validate_ignore_add = validator(IgnoreAddResults)
 validate_ignore_delete = validator(IgnoreDeleteResults)
 validate_ignore_list = validator(IgnoreListResults)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to