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 44633f6  Add an admin route to regenerate all KEYS files
44633f6 is described below

commit 44633f6731cbc33396345cceefb2f11543ce9361
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Jun 17 18:57:33 2025 +0100

    Add an admin route to regenerate all KEYS files
---
 atr/blueprints/admin/admin.py | 44 +++++++++++++++++++++++++++++++++++++++++
 atr/routes/keys.py            | 46 ++++++++++++++++++++++++-------------------
 2 files changed, 70 insertions(+), 20 deletions(-)

diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index eaf36f8..848ce8d 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -44,6 +44,7 @@ import atr.db as db
 import atr.db.interaction as interaction
 import atr.db.models as models
 import atr.ldap as ldap
+import atr.routes.keys as keys
 import atr.routes.mapping as mapping
 import atr.template as template
 import atr.util as util
@@ -357,6 +358,31 @@ async def admin_env() -> quart.wrappers.response.Response:
     return quart.Response("\n".join(env_vars), mimetype="text/plain")
 
 
[email protected]("/keys/regenerate-all", methods=["GET", "POST"])
+async def admin_keys_regenerate_all() -> quart.Response:
+    """Regenerate the KEYS file for all committees."""
+    if quart.request.method != "POST":
+        empty_form = await util.EmptyForm.create_form()
+        return quart.Response(
+            f"""
+<form method="post">
+  <button type="submit">Regenerate all KEYS files</button>
+  {empty_form.hidden_tag()}
+</form>
+""",
+            mimetype="text/html",
+        )
+
+    try:
+        okay, failed = await _regenerate_keys_all()
+        return quart.Response(
+            f"KEYS file regeneration results: {okay} okay, {failed} failed",
+            mimetype="text/plain",
+        )
+    except Exception as e:
+        return quart.Response(f"Exception during KEYS file regeneration: 
{e!s}", mimetype="text/plain")
+
+
 @admin.BLUEPRINT.route("/keys/update", methods=["GET", "POST"])
 async def admin_keys_update() -> str | response.Response | tuple[Mapping[str, 
Any], int]:
     """Update keys from remote data."""
@@ -725,6 +751,24 @@ def _project_status(
     return models.ProjectStatus.ACTIVE
 
 
+async def _regenerate_keys_all() -> tuple[int, int]:
+    okay = 0
+    failed = 0
+    async with db.session() as data:
+        committees = await data.committee().all()
+        for committee in committees:
+            try:
+                error_msg = await keys.autogenerate_keys_file(committee.name, 
caller_data=data)
+            except Exception:
+                failed += 1
+                continue
+            if error_msg:
+                failed += 1
+            else:
+                okay += 1
+    return okay, failed
+
+
 def _session_data(
     ldap_data: dict[str, Any],
     new_uid: str,
diff --git a/atr/routes/keys.py b/atr/routes/keys.py
index dd76343..bb922ff 100644
--- a/atr/routes/keys.py
+++ b/atr/routes/keys.py
@@ -20,6 +20,7 @@
 import asyncio
 import base64
 import binascii
+import contextlib
 import datetime
 import hashlib
 import logging
@@ -170,7 +171,7 @@ async def add(session: routes.CommitterSession) -> str:
                 if key_info:
                     await quart.flash(f"GPG key {key_info.get('fingerprint', 
'')} added successfully.", "success")
                     for committee_name in selected_committees_data:
-                        await _autogenerate_keys_file(committee_name)
+                        await autogenerate_keys_file(committee_name)
             if not added_keys:
                 await quart.flash("No keys were added.", "error")
             # Clear form data on success by creating a new empty form instance
@@ -193,6 +194,27 @@ async def add(session: routes.CommitterSession) -> str:
     )
 
 
+async def autogenerate_keys_file(committee_name: str, caller_data: db.Session 
| None = None) -> str | None:
+    base_downloads_dir = util.get_downloads_dir()
+
+    if caller_data is None:
+        manager = db.session()
+    else:
+        manager = contextlib.nullcontext(caller_data)
+
+    async with manager as data:
+        full_keys_file_content = await _keys_formatter(committee_name, data)
+        committee_keys_dir = base_downloads_dir / committee_name
+        committee_keys_path = committee_keys_dir / "KEYS"
+        error_msg = await _write_keys_file(
+            committee_keys_dir=committee_keys_dir,
+            full_keys_file_content=full_keys_file_content,
+            committee_keys_path=committee_keys_path,
+            committee_name=committee_name,
+        )
+    return error_msg
+
+
 @routes.committer("/keys/delete", methods=["POST"])
 async def delete(session: routes.CommitterSession) -> response.Response:
     """Delete a public signing key or SSH key from the user's account."""
@@ -213,7 +235,7 @@ async def delete(session: routes.CommitterSession) -> 
response.Response:
                 # Delete the GPG key
                 await data.delete(key)
                 for committee in key.committees:
-                    await _autogenerate_keys_file(committee.name)
+                    await autogenerate_keys_file(committee.name, 
caller_data=data)
                 return await session.redirect(keys, success="GPG key deleted 
successfully")
 
             # If not a GPG key, try to get an SSH key
@@ -259,7 +281,7 @@ async def import_selected_revision(
         )
     except interaction.InteractionError as e:
         return await session.redirect(compose.selected, error=str(e))
-    await _autogenerate_keys_file(release.committee.name)
+    await autogenerate_keys_file(release.committee.name)
     message = f"Uploaded {success_count} keys,"
     if error_count > 0:
         message += f" failed to upload {error_count} keys for {', 
'.join(submitted_committees)}"
@@ -413,7 +435,7 @@ async def update_committee_keys(session: 
routes.CommitterSession, committee_name
     if committee_name not in (session.committees + session.projects):
         quart.abort(403, description=f"You are not authorised to update the 
KEYS file for {committee_name}")
 
-    error_msg = await _autogenerate_keys_file(committee_name)
+    error_msg = await autogenerate_keys_file(committee_name)
 
     if error_msg:
         await quart.flash(error_msg, "error")
@@ -515,22 +537,6 @@ async def upload(session: routes.CommitterSession) -> str:
     return await render()
 
 
-async def _autogenerate_keys_file(committee_name: str) -> str | None:
-    base_downloads_dir = util.get_downloads_dir()
-
-    async with db.session() as data:
-        full_keys_file_content = await _keys_formatter(committee_name, data)
-        committee_keys_dir = base_downloads_dir / committee_name
-        committee_keys_path = committee_keys_dir / "KEYS"
-        error_msg = await _write_keys_file(
-            committee_keys_dir=committee_keys_dir,
-            full_keys_file_content=full_keys_file_content,
-            committee_keys_path=committee_keys_path,
-            committee_name=committee_name,
-        )
-    return error_msg
-
-
 async def _format_keys_file(
     committee_name_for_header: str,
     key_count_for_header: int,


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

Reply via email to