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]