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]