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-releases.git


The following commit(s) were added to refs/heads/main by this push:
     new db8d543  Show more detail in errors when fetching remote OpenPGP keys
db8d543 is described below

commit db8d5430e44973cecb4a5f7e3724e1a9ee909cc6
Author: Sean B. Palmer <[email protected]>
AuthorDate: Sun Nov 16 14:53:46 2025 +0000

    Show more detail in errors when fetching remote OpenPGP keys
---
 atr/admin/__init__.py  | 20 ++++++++++--
 scripts/keys_import.py | 89 +++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 98 insertions(+), 11 deletions(-)

diff --git a/atr/admin/__init__.py b/atr/admin/__init__.py
index 1be3b6c..b9bbbf4 100644
--- a/atr/admin/__init__.py
+++ b/atr/admin/__init__.py
@@ -528,9 +528,10 @@ async def _keys_update(session: web.Committer) -> str | 
web.WerkzeugResponse | t
             "category": "success",
         }, 200
     except Exception as e:
-        log.exception("Failed to start key update process")
+        detail = _format_exception_location(e)
+        log.exception("Failed to start key update process: %s", detail)
         return {
-            "message": f"Failed to update keys: {e!s}",
+            "message": f"Failed to update keys: {detail}",
             "category": "error",
         }, 200
 
@@ -545,6 +546,21 @@ async def ldap_post(session: web.Committer) -> str:
     return await _ldap(session)
 
 
+def _format_exception_location(exc: BaseException) -> str:
+    tb = exc.__traceback__
+    last_tb = None
+    while tb is not None:
+        last_tb = tb
+        tb = tb.tb_next
+    if last_tb is None:
+        return f"{type(exc).__name__}: {exc}"
+    frame = last_tb.tb_frame
+    filename = pathlib.Path(frame.f_code.co_filename).name
+    lineno = last_tb.tb_lineno
+    func = frame.f_code.co_name
+    return f"{type(exc).__name__} at {filename}:{lineno} in {func}: {exc}"
+
+
 async def _ldap(session: web.Committer) -> str:
     form = await LdapLookupForm.create_form(data=quart.request.args)
 
diff --git a/scripts/keys_import.py b/scripts/keys_import.py
index 70f29d3..c54d0b9 100755
--- a/scripts/keys_import.py
+++ b/scripts/keys_import.py
@@ -21,9 +21,11 @@
 import asyncio
 import contextlib
 import os
+import pathlib
 import sys
 import time
 import traceback
+from typing import TYPE_CHECKING
 
 sys.path.append(".")
 
@@ -31,15 +33,37 @@ sys.path.append(".")
 import atr.config as config
 import atr.db as db
 import atr.storage as storage
+import atr.storage.outcome as outcome
+import atr.storage.types as types
 import atr.util as util
 
+if TYPE_CHECKING:
+    from types import TracebackType
 
-def get(entry: dict, prop: str) -> str | None:
-    if prop in entry:
-        values = entry[prop]
-        if values:
-            return values[0]
-    return None
+
+def find_project_root() -> pathlib.Path:
+    current = pathlib.Path(__file__).resolve()
+    for candidate in [current, *current.parents]:
+        if (candidate / "atr").is_dir():
+            return candidate
+    return current.parent
+
+
+PROJECT_ROOT = find_project_root()
+
+
+def is_atr_path(path: str) -> bool:
+    try:
+        resolved = pathlib.Path(path).resolve()
+    except OSError:
+        return False
+    if any((part == ".venv") for part in resolved.parts):
+        return False
+    try:
+        relative = resolved.relative_to(PROJECT_ROOT)
+    except ValueError:
+        return False
+    return "atr" in relative.parts
 
 
 def print_and_flush(message: str) -> None:
@@ -47,6 +71,53 @@ def print_and_flush(message: str) -> None:
     sys.stdout.flush()
 
 
+def format_exception_location(exc: BaseException) -> str:
+    tb = exc.__traceback__
+    frames: list[TracebackType] = []
+    while tb is not None:
+        frames.append(tb)
+        tb = tb.tb_next
+    if not frames:
+        return f"{type(exc).__name__}: {exc}"
+
+    chosen_tb = None
+    for frame_tb in reversed(frames):
+        filename = frame_tb.tb_frame.f_code.co_filename
+        if is_atr_path(filename):
+            chosen_tb = frame_tb
+            break
+    if chosen_tb is None:
+        chosen_tb = frames[-1]
+
+    frame = chosen_tb.tb_frame
+    filename_path = pathlib.Path(frame.f_code.co_filename).resolve()
+    try:
+        filename_relative = filename_path.relative_to(PROJECT_ROOT)
+    except ValueError:
+        filename_relative = filename_path.name
+    filename = str(filename_relative)
+    lineno = chosen_tb.tb_lineno
+    func = frame.f_code.co_name
+    return f"{type(exc).__name__} at {filename}:{lineno} in {func}: {exc}"
+
+
+def log_outcome_errors(outcomes: outcome.List[types.Key], committee_name: str) 
-> None:
+    for error in outcomes.errors():
+        fingerprint = "unknown"
+        detail_exception: BaseException = error
+        if isinstance(error, types.PublicKeyError):
+            fingerprint = error.key.key_model.fingerprint
+            detail_exception = error.original_error
+        elif isinstance(error, BaseException):
+            detail_exception = error
+        else:
+            print_and_flush(f"ERROR! fingerprint={fingerprint} 
committee={committee_name} detail={error!r}")
+            continue
+
+        detail = format_exception_location(detail_exception)
+        print_and_flush(f"ERROR! fingerprint={fingerprint} 
committee={committee_name} detail={detail}")
+
+
 @contextlib.contextmanager
 def log_to_file(conf: config.AppConfig):
     log_file_path = os.path.join(conf.STATE_DIR, "keys_import.log")
@@ -111,10 +182,9 @@ async def keys_import(conf: config.AppConfig, asf_uid: 
str) -> None:
             wafa = write.as_foundation_admin(committee_name)
             keys_file_text = content.decode("utf-8", errors="replace")
             outcomes = await wafa.keys.ensure_associated(keys_file_text)
+            log_outcome_errors(outcomes, committee_name)
             yes = outcomes.result_count
             no = outcomes.error_count
-            if no:
-                outcomes.errors_print()
 
             # Print and record the number of keys that were okay and failed
             print_and_flush(f"{committee_name} {yes} {no}")
@@ -133,7 +203,8 @@ async def amain() -> None:
         try:
             await keys_import(conf, sys.argv[1])
         except Exception as e:
-            print_and_flush(f"Error: {e}")
+            detail = format_exception_location(e)
+            print_and_flush(f"Error: {detail}")
             traceback.print_exc()
             sys.stdout.flush()
             sys.exit(1)


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

Reply via email to