This is an automated email from the ASF dual-hosted git repository. sbp pushed a commit to branch sbp in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
commit 5b8a282d676e94f7623e38ceb1e833a78cfb9045 Author: Sean B. Palmer <[email protected]> AuthorDate: Tue Feb 10 17:30:59 2026 +0000 Add an extra file of attestable JSON data mapping paths to hashes --- atr/attestable.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ atr/models/attestable.py | 5 ++++ atr/server.py | 5 ++++ 3 files changed, 69 insertions(+) diff --git a/atr/attestable.py b/atr/attestable.py index d576236..f487d9e 100644 --- a/atr/attestable.py +++ b/atr/attestable.py @@ -39,6 +39,10 @@ def attestable_path(project_name: str, version_name: str, revision_number: str) return util.get_attestable_dir() / project_name / version_name / f"{revision_number}.json" +def attestable_paths_path(project_name: str, version_name: str, revision_number: str) -> pathlib.Path: + return util.get_attestable_dir() / project_name / version_name / f"{revision_number}.paths.json" + + async def compute_file_hash(path: pathlib.Path) -> str: hasher = blake3.blake3() async with aiofiles.open(path, "rb") as f: @@ -75,6 +79,58 @@ async def load( return None +async def load_paths( + project_name: str, + version_name: str, + revision_number: str, +) -> dict[str, str] | None: + file_path = attestable_paths_path(project_name, version_name, revision_number) + if await aiofiles.os.path.isfile(file_path): + try: + async with aiofiles.open(file_path, encoding="utf-8") as f: + data = json.loads(await f.read()) + return models.AttestablePathsV1.model_validate(data).paths + except (json.JSONDecodeError, pydantic.ValidationError) as e: + # log.warning(f"Could not parse {file_path}, trying combined file: {e}") + log.warning(f"Could not parse {file_path}: {e}") + # combined = await load(project_name, version_name, revision_number) + # if combined is not None: + # return combined.paths + return None + + +def migrate_to_paths_files() -> int: + attestable_dir = util.get_attestable_dir() + if not attestable_dir.is_dir(): + return 0 + count = 0 + for project_dir in sorted(attestable_dir.iterdir()): + if not project_dir.is_dir(): + continue + for version_dir in sorted(project_dir.iterdir()): + if not version_dir.is_dir(): + continue + for json_file in sorted(version_dir.glob("*.json")): + if "." in json_file.stem: + continue + target = version_dir / f"{json_file.stem}.paths.json" + if target.exists(): + continue + try: + with open(json_file, encoding="utf-8") as f: + data = json.loads(f.read()) + validated = models.AttestableV1.model_validate(data) + paths_result = models.AttestablePathsV1(paths=validated.paths) + tmp = target.with_suffix(".tmp") + with open(tmp, "w", encoding="utf-8") as f: + f.write(paths_result.model_dump_json(indent=2)) + tmp.replace(target) + count += 1 + except (json.JSONDecodeError, pydantic.ValidationError): + continue + return count + + async def write( release_directory: pathlib.Path, project_name: str, @@ -89,6 +145,9 @@ async def write( result = await _generate(release_directory, revision_number, uploader_uid, previous) file_path = attestable_path(project_name, version_name, revision_number) await util.atomic_write_file(file_path, result.model_dump_json(indent=2)) + paths_result = models.AttestablePathsV1(paths=result.paths) + paths_file_path = attestable_paths_path(project_name, version_name, revision_number) + await util.atomic_write_file(paths_file_path, paths_result.model_dump_json(indent=2)) def _compute_hashes_with_attribution( diff --git a/atr/models/attestable.py b/atr/models/attestable.py index 3cff655..4bc574b 100644 --- a/atr/models/attestable.py +++ b/atr/models/attestable.py @@ -27,6 +27,11 @@ class HashEntry(schema.Strict): uploaders: list[Annotated[tuple[str, str], pydantic.BeforeValidator(tuple)]] +class AttestablePathsV1(schema.Strict): + version: Literal[1] = 1 + paths: dict[str, str] = schema.factory(dict) + + class AttestableV1(schema.Strict): version: Literal[1] = 1 paths: dict[str, str] = schema.factory(dict) diff --git a/atr/server.py b/atr/server.py index 2abff42..c09357c 100644 --- a/atr/server.py +++ b/atr/server.py @@ -46,6 +46,7 @@ import quart_wtf import werkzeug.routing as routing import atr +import atr.attestable as attestable import atr.blueprints as blueprints import atr.cache as cache import atr.config as config @@ -260,6 +261,10 @@ def _app_setup_lifecycle(app: base.QuartApp, app_config: type[config.AppConfig]) await asyncio.to_thread(_set_file_permissions_to_read_only) + migrated = await asyncio.to_thread(attestable.migrate_to_paths_files) + if migrated > 0: + log.info(f"Migrated {migrated} attestable files to paths format") + await cache.admins_startup_load() admins_task = asyncio.create_task(cache.admins_refresh_loop()) app.extensions["admins_task"] = admins_task --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
