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 d64ad02  Use the storage interface for the form to regenerate all KEYS 
files
d64ad02 is described below

commit d64ad0215cad38fea9314762472bd05b45d643d6
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Jul 22 15:18:28 2025 +0100

    Use the storage interface for the form to regenerate all KEYS files
---
 atr/blueprints/admin/admin.py | 33 ++++++++++++++++++++++++---------
 atr/storage/__init__.py       | 20 ++++++++++++++++++++
 atr/storage/types.py          | 25 ++++++++++++++-----------
 atr/storage/writers/keys.py   | 43 ++++++++++++++++++++++++++++++-------------
 4 files changed, 88 insertions(+), 33 deletions(-)

diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index 1e9b57b..03edd75 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -47,6 +47,7 @@ import atr.ldap as ldap
 import atr.models.sql as sql
 import atr.routes.keys as keys
 import atr.routes.mapping as mapping
+import atr.storage as storage
 import atr.storage.types as types
 import atr.template as template
 import atr.util as util
@@ -422,15 +423,29 @@ async def admin_keys_regenerate_all() -> quart.Response:
             mimetype="text/html",
         )
 
-    try:
-        okay, failures = await _regenerate_keys_all()
-        msg = f"KEYS file regeneration results: {okay} okay, {len(failures)} 
failed"
-        if failures:
-            msg += f"\nFailures:\n{'\n'.join(failures)}"
-        return quart.Response(msg, mimetype="text/plain")
-    except Exception as e:
-        _LOGGER.exception("Exception during KEYS file regeneration:")
-        return quart.Response(f"Exception during KEYS file regeneration: 
{e!s}", mimetype="text/plain")
+    async with db.session() as data:
+        committee_names = [c.name for c in await data.committee().all()]
+
+    web_session = await session.read()
+    if web_session is None:
+        raise base.ASFQuartException("Not authenticated", 401)
+    asf_uid = web_session.uid
+    if asf_uid is None:
+        raise base.ASFQuartException("Invalid session, uid is None", 500)
+
+    outcomes = types.Outcomes[str]()
+    async with storage.write(asf_uid) as write:
+        for committee_name in committee_names:
+            wacm = write.as_committee_member(committee_name).writer_or_raise()
+            outcomes.append(await wacm.keys.autogenerate_keys_file())
+
+    response_lines = []
+    for ocr in outcomes.results():
+        response_lines.append(f"Regenerated: {ocr}")
+    for oce in outcomes.exceptions():
+        response_lines.append(f"Error regenerating: {type(oce).__name__} 
{oce}")
+
+    return quart.Response("\n".join(response_lines), mimetype="text/plain")
 
 
 @admin.BLUEPRINT.route("/keys/update", methods=["GET", "POST"])
diff --git a/atr/storage/__init__.py b/atr/storage/__init__.py
index ab2da74..642780a 100644
--- a/atr/storage/__init__.py
+++ b/atr/storage/__init__.py
@@ -197,6 +197,26 @@ class WriteAsCommitteeMember(WriteAsCommitteeParticipant):
         return VALIDATE_AT_RUNTIME
 
 
+# class WriteAsFoundationAdmin(WriteAsFoundationMember):
+#     def __init__(self, data: db.Session, asf_uid: str):
+#         self.__data = data
+#         self.__asf_uid = asf_uid
+#         self.__authenticated = True
+#         self.keys = writers.keys.FoundationAdmin(
+#             self,
+#             self.__data,
+#             self.__asf_uid,
+#         )
+
+#     @property
+#     def authenticated(self) -> bool:
+#         return self.__authenticated
+
+#     @property
+#     def validate_at_runtime(self) -> bool:
+#         return VALIDATE_AT_RUNTIME
+
+
 class Write:
     # Read and Write have authenticator methods which return access outcomes
     def __init__(self, data: db.Session, asf_uid: str | None = None):
diff --git a/atr/storage/types.py b/atr/storage/types.py
index b639474..82e7ee0 100644
--- a/atr/storage/types.py
+++ b/atr/storage/types.py
@@ -79,7 +79,7 @@ class OutcomeResult[T](OutcomeCore[T]):
         return None
 
 
-class OutcomeException[T, E: Exception](OutcomeCore[T]):
+class OutcomeException[T, E: Exception = Exception](OutcomeCore[T]):
     __exception: E
 
     def __init__(self, exception: E, name: str | None = None):
@@ -109,6 +109,9 @@ class OutcomeException[T, E: Exception](OutcomeCore[T]):
         return type(self.__exception)
 
 
+type Outcome[T] = OutcomeResult[T] | OutcomeException[T, Exception]
+
+
 class Outcomes[T]:
     __outcomes: list[OutcomeResult[T] | OutcomeException[T, Exception]]
 
@@ -123,11 +126,14 @@ class Outcomes[T]:
     def all_ok(self) -> bool:
         return all(outcome.ok for outcome in self.__outcomes)
 
-    def append(self, result_or_error: T | Exception, name: str | None = None) 
-> None:
-        if isinstance(result_or_error, Exception):
-            self.__outcomes.append(OutcomeException(result_or_error, name))
+    def append(self, outcome: OutcomeResult[T] | OutcomeException[T, 
Exception]) -> None:
+        self.__outcomes.append(outcome)
+
+    def append_roe(self, roe: T | Exception, name: str | None = None) -> None:
+        if isinstance(roe, Exception):
+            self.__outcomes.append(OutcomeException[T, Exception](roe, name))
         else:
-            self.__outcomes.append(OutcomeResult(result_or_error, name))
+            self.__outcomes.append(OutcomeResult[T](roe, name))
 
     @property
     def exception_count(self) -> int:
@@ -142,9 +148,9 @@ class Outcomes[T]:
                     exceptions_list.append(exception_or_none)
         return exceptions_list
 
-    def extend(self, result_or_error_list: Sequence[T | Exception]) -> None:
-        for result_or_error in result_or_error_list:
-            self.append(result_or_error)
+    def extend(self, roes: Sequence[T | Exception]) -> None:
+        for roe in roes:
+            self.append_roe(roe)
 
     def named_results(self) -> dict[str, T]:
         named = {}
@@ -193,9 +199,6 @@ class Outcomes[T]:
                     self.__outcomes[i] = OutcomeResult(result, outcome.name)
 
 
-type Outcome[T] = OutcomeResult[T] | OutcomeException[T, Exception]
-
-
 class KeyStatus(enum.Flag):
     PARSED = 0
     INSERTED = enum.auto()
diff --git a/atr/storage/writers/keys.py b/atr/storage/writers/keys.py
index 2794c94..fe8d9d1 100644
--- a/atr/storage/writers/keys.py
+++ b/atr/storage/writers/keys.py
@@ -263,7 +263,7 @@ class CommitteeMember(CommitteeParticipant):
         except Exception as e:
             return types.OutcomeException(e)
         try:
-            autogenerated_outcome = await self.__autogenerate_keys_file()
+            autogenerated_outcome = await self.autogenerate_keys_file()
         except Exception as e:
             return types.OutcomeException(e)
         return types.OutcomeResult(
@@ -288,20 +288,24 @@ class CommitteeMember(CommitteeParticipant):
     async def ensure_stored(self, keys_file_text: str) -> 
types.Outcomes[types.Key]:
         return await self.__ensure(keys_file_text, associate=False)
 
-    async def __autogenerate_keys_file(
+    async def autogenerate_keys_file(
         self,
     ) -> types.Outcome[str]:
-        base_downloads_dir = util.get_downloads_dir()
+        try:
+            base_downloads_dir = util.get_downloads_dir()
 
-        committee = await self.committee()
-        is_podling = committee.is_podling
+            committee = await self.committee()
+            is_podling = committee.is_podling
+
+            full_keys_file_content = await self.__keys_formatter()
+            if is_podling:
+                committee_keys_dir = base_downloads_dir / "incubator" / 
self.__committee_name
+            else:
+                committee_keys_dir = base_downloads_dir / self.__committee_name
+            committee_keys_path = committee_keys_dir / "KEYS"
+        except Exception as e:
+            return types.OutcomeException(e)
 
-        full_keys_file_content = await self.__keys_formatter()
-        if is_podling:
-            committee_keys_dir = base_downloads_dir / "incubator" / 
self.__committee_name
-        else:
-            committee_keys_dir = base_downloads_dir / self.__committee_name
-        committee_keys_path = committee_keys_dir / "KEYS"
         try:
             await asyncio.to_thread(committee_keys_dir.mkdir, parents=True, 
exist_ok=True)
             await asyncio.to_thread(util.chmod_directories, 
committee_keys_dir, permissions=0o755)
@@ -430,14 +434,14 @@ class CommitteeMember(CommitteeParticipant):
             ldap_data = await util.email_to_uid_map()
             key_blocks = util.parse_key_blocks(keys_file_text)
         except Exception as e:
-            outcomes.append(e)
+            outcomes.append_roe(e)
             return outcomes
         for key_block in key_blocks:
             try:
                 key_models = await asyncio.to_thread(self.__block_models, 
key_block, ldap_data)
                 outcomes.extend(key_models)
             except Exception as e:
-                outcomes.append(e)
+                outcomes.append_roe(e)
         # Try adding the keys to the database
         # If not, all keys will be replaced with a PostParseError
         outcomes = await self.__database_add_models(outcomes, 
associate=associate)
@@ -534,3 +538,16 @@ and was published by the committee.\
             key_count_for_header=key_count_for_header,
             key_blocks_str=key_blocks_str,
         )
+
+
+# class FoundationAdmin(FoundationMember):
+#     def __init__(
+#         self, credentials: storage.WriteAsFoundationAdmin, data: db.Session, 
asf_uid: str
+#     ):
+#         self.__data = data
+#         self.__credentials = credentials
+#         self.__asf_uid = asf_uid
+
+#     @performance_async
+#     async def ensure_stored_one(self, key_file_text: str) -> 
types.Outcome[types.Key]:
+#         return await self.__ensure_one(key_file_text, associate=False)


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

Reply via email to