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]