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 ea8f674 Move more KEYS functionality to the storage interface
ea8f674 is described below
commit ea8f674859dd40caa232a461182a67aee90ab475
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Jul 22 19:09:50 2025 +0100
Move more KEYS functionality to the storage interface
---
atr/routes/keys.py | 31 ++++++-------------------------
atr/storage/__init__.py | 22 ++++++++++++++++++++++
atr/storage/writers/keys.py | 36 ++++++++++++++++++++++++++++++++++++
3 files changed, 64 insertions(+), 25 deletions(-)
diff --git a/atr/routes/keys.py b/atr/routes/keys.py
index e361989..0f45d46 100644
--- a/atr/routes/keys.py
+++ b/atr/routes/keys.py
@@ -26,7 +26,6 @@ import logging
import logging.handlers
from collections.abc import Awaitable, Callable, Sequence
-import aiofiles.os
import aiohttp
import asfquart as asfquart
import asfquart.base as base
@@ -38,7 +37,6 @@ import wtforms
import atr.db as db
import atr.models.sql as sql
-import atr.revision as revision
import atr.routes as routes
import atr.routes.compose as compose
import atr.storage as storage
@@ -316,33 +314,16 @@ async def export(session: routes.CommitterSession,
committee_name: str) -> quart
async def import_selected_revision(
session: routes.CommitterSession, project_name: str, version_name: str
) -> response.Response:
- await session.check_access(project_name)
-
await util.validate_empty_form()
- release = await session.release(project_name, version_name,
with_committee=True)
- keys_path = util.release_directory(release) / "KEYS"
- async with aiofiles.open(keys_path, encoding="utf-8") as f:
- keys_text = await f.read()
- if release.committee is None:
- raise routes.FlashError("No committee found for release")
async with storage.write(session.uid) as write:
- wacm =
write.as_committee_member(release.committee.name).result_or_raise()
- outcomes: types.Outcomes[types.Key] = await
wacm.keys.ensure_associated(keys_text)
- success_count = outcomes.result_count
- error_count = outcomes.exception_count
+ access_outcome = await write.as_project_committee_member(project_name)
+ wacm = access_outcome.result_or_raise()
+ outcomes: types.Outcomes[types.Key] = await
wacm.keys.import_keys_file(project_name, version_name)
- message = f"Uploaded {success_count} keys,"
- if error_count > 0:
- message += f" failed to upload {error_count} keys for
{release.committee.name}"
- # Remove the KEYS file if 100% imported
- if (success_count > 0) and (error_count == 0):
- description = "Removed KEYS file after successful import through web
interface"
- async with revision.create_and_manage(
- project_name, version_name, session.uid, description=description
- ) as creating:
- path_in_new_revision = creating.interim_path / "KEYS"
- await aiofiles.os.remove(path_in_new_revision)
+ message = f"Uploaded {outcomes.result_count} keys,"
+ if outcomes.exception_count > 0:
+ message += f" failed to upload {outcomes.exception_count} keys for
{wacm.committee_name}"
return await session.redirect(
compose.selected,
success=message,
diff --git a/atr/storage/__init__.py b/atr/storage/__init__.py
index 05c1c18..d22652c 100644
--- a/atr/storage/__init__.py
+++ b/atr/storage/__init__.py
@@ -142,6 +142,10 @@ class WriteAsCommitteeParticipant(WriteAsFoundationMember):
def authenticated(self) -> bool:
return self.__authenticated
+ @property
+ def committee_name(self) -> str:
+ return self.__committee_name
+
@property
def validate_at_runtime(self) -> bool:
return VALIDATE_AT_RUNTIME
@@ -166,6 +170,10 @@ class WriteAsCommitteeMember(WriteAsCommitteeParticipant):
def authenticated(self) -> bool:
return self.__authenticated
+ @property
+ def committee_name(self) -> str:
+ return self.__committee_name
+
@property
def validate_at_runtime(self) -> bool:
return VALIDATE_AT_RUNTIME
@@ -208,6 +216,20 @@ class Write:
return types.OutcomeException(e)
return types.OutcomeResult(wacm)
+ async def as_project_committee_member(self, project_name: str) ->
types.Outcome[WriteAsCommitteeMember]:
+ project = await self.__data.project(project_name,
_committee=True).demand(
+ AccessError(f"Project not found: {project_name}")
+ )
+ if project.committee is None:
+ return types.OutcomeException(AccessError("No committee found for
project"))
+ if self.__asf_uid is None:
+ return types.OutcomeException(AccessError("No ASF UID"))
+ try:
+ wacm = WriteAsCommitteeMember(self, self.__data, self.__asf_uid,
project.committee.name)
+ except Exception as e:
+ return types.OutcomeException(e)
+ return types.OutcomeResult(wacm)
+
def as_foundation_member(self) -> types.Outcome[WriteAsFoundationMember]:
if self.__asf_uid is None:
return types.OutcomeException(AccessError("No ASF UID"))
diff --git a/atr/storage/writers/keys.py b/atr/storage/writers/keys.py
index 57eeb0f..7cd088e 100644
--- a/atr/storage/writers/keys.py
+++ b/atr/storage/writers/keys.py
@@ -29,6 +29,8 @@ import textwrap
import time
from typing import TYPE_CHECKING, Any, Final, NoReturn
+import aiofiles
+import aiofiles.os
import pgpy
import pgpy.constants as constants
import sqlalchemy.dialects.sqlite as sqlite
@@ -436,6 +438,10 @@ class CommitteeMember(CommitteeParticipant):
storage.AccessError(f"Committee not found:
{self.__committee_name}")
)
+ @property
+ def committee_name(self) -> str:
+ return self.__committee_name
+
@performance_async
async def ensure_associated(self, keys_file_text: str) ->
types.Outcomes[types.Key]:
outcomes: types.Outcomes[types.Key] = await
self.__ensure(keys_file_text, associate=True)
@@ -450,6 +456,36 @@ class CommitteeMember(CommitteeParticipant):
await self.autogenerate_keys_file()
return outcomes
+ @performance_async
+ async def import_keys_file(self, project_name: str, version_name: str) ->
types.Outcomes[types.Key]:
+ import atr.revision as revision
+
+ release = await self.__data.release(
+ project_name=project_name,
+ version=version_name,
+ _committee=True,
+ ).demand(storage.AccessError(f"Release not found: {project_name}
{version_name}"))
+ keys_path = util.release_directory(release) / "KEYS"
+ async with aiofiles.open(keys_path, encoding="utf-8") as f:
+ keys_file_text = await f.read()
+ if release.committee is None:
+ raise storage.AccessError("No committee found for release")
+ if release.committee.name != self.__committee_name:
+ raise storage.AccessError(
+ f"Release {project_name} {version_name} is not associated with
committee {self.__committee_name}"
+ )
+
+ outcomes = await self.ensure_associated(keys_file_text)
+ # Remove the KEYS file if 100% imported
+ if (outcomes.result_count > 0) and (outcomes.exception_count == 0):
+ description = "Removed KEYS file after successful import through
web interface"
+ async with revision.create_and_manage(
+ project_name, version_name, self.__asf_uid,
description=description
+ ) as creating:
+ path_in_new_revision = creating.interim_path / "KEYS"
+ await aiofiles.os.remove(path_in_new_revision)
+ return outcomes
+
@performance
def __block_models(self, key_block: str, ldap_data: dict[str, str]) ->
list[types.Key | Exception]:
# This cache is only held for the session
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]