136 lines
4.9 KiB
Python
136 lines
4.9 KiB
Python
"""Validate a privacy-safe public-avatar migration manifest."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
API_ROOT = REPO_ROOT / "apps" / "api"
|
|
if str(API_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(API_ROOT))
|
|
|
|
os.chdir(API_ROOT)
|
|
|
|
from app.config import settings # noqa: E402
|
|
from app.upload_storage import ( # noqa: E402
|
|
validate_current_avatar_references,
|
|
validate_upload_manifest,
|
|
)
|
|
from public_runtime_database_identity import ( # noqa: E402
|
|
connected_database_target_sha256,
|
|
)
|
|
|
|
|
|
async def _load_current_runtime_db_state() -> tuple[list[str], str]:
|
|
import asyncpg
|
|
|
|
connection = await asyncpg.connect(settings.database_url)
|
|
try:
|
|
async with connection.transaction(isolation="repeatable_read", readonly=True):
|
|
target_sha256 = await connected_database_target_sha256(connection)
|
|
rows = await connection.fetch(
|
|
"""
|
|
SELECT avatar_url
|
|
FROM app.app_user
|
|
WHERE btrim(avatar_url) LIKE '/uploads/%'
|
|
ORDER BY avatar_url
|
|
"""
|
|
)
|
|
return [str(row["avatar_url"]) for row in rows], target_sha256
|
|
finally:
|
|
await connection.close()
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--upload-root", required=True)
|
|
parser.add_argument("--manifest-path", required=True)
|
|
parser.add_argument("--expected-manifest-sha256", required=True)
|
|
parser.add_argument("--expected-write-freeze-path", required=True)
|
|
args = parser.parse_args()
|
|
try:
|
|
current_urls, database_target = asyncio.run(_load_current_runtime_db_state())
|
|
proof = validate_upload_manifest(
|
|
upload_root=Path(args.upload_root),
|
|
manifest_path=Path(args.manifest_path),
|
|
expected_manifest_sha256=args.expected_manifest_sha256,
|
|
expected_write_freeze_path=Path(args.expected_write_freeze_path),
|
|
expected_database_target_sha256=database_target,
|
|
verify_preserved_objects=True,
|
|
reject_unbound_extras=False,
|
|
)
|
|
current = validate_current_avatar_references(
|
|
upload_root=Path(args.upload_root),
|
|
avatar_urls=current_urls,
|
|
initial_manifest=proof,
|
|
expected_database_target_sha256=database_target,
|
|
)
|
|
except (OSError, ValueError):
|
|
print(
|
|
json.dumps(
|
|
{"status": "failed", "reason": "upload_storage_contract_failed"},
|
|
ensure_ascii=True,
|
|
separators=(",", ":"),
|
|
)
|
|
)
|
|
return 1
|
|
except Exception:
|
|
print(
|
|
json.dumps(
|
|
{"status": "failed", "reason": "current_db_inventory_unavailable"},
|
|
ensure_ascii=True,
|
|
separators=(",", ":"),
|
|
)
|
|
)
|
|
return 1
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"status": "passed",
|
|
"manifest_sha256": proof.manifest_sha256,
|
|
"root_path_sha256": proof.root_path_sha256,
|
|
"required_object_count": proof.required_object_count,
|
|
"preserved_object_count": proof.preserved_object_count,
|
|
"preserved_total_size_bytes": proof.preserved_total_size_bytes,
|
|
"preserved_decode_valid_count": (
|
|
proof.preserved_decode_valid_count
|
|
),
|
|
"preserved_decode_invalid_count": (
|
|
proof.preserved_decode_invalid_count
|
|
),
|
|
"required_decode_invalid_object_count": (
|
|
proof.required_decode_invalid_object_count
|
|
),
|
|
"required_decode_invalid_reference_count": (
|
|
proof.required_decode_invalid_reference_count
|
|
),
|
|
"preserved_object_set_sha256": proof.preserved_object_set_sha256,
|
|
"required_reference_count": proof.required_reference_count,
|
|
"reference_set_sha256": proof.reference_set_sha256,
|
|
"write_freeze_token_sha256": proof.write_freeze_token_sha256,
|
|
"current_object_count": current.object_count,
|
|
"current_reference_count": current.reference_count,
|
|
"current_reference_set_sha256": current.reference_set_sha256,
|
|
"current_decode_invalid_object_count": (
|
|
current.decode_invalid_object_count
|
|
),
|
|
"current_decode_invalid_reference_count": (
|
|
current.decode_invalid_reference_count
|
|
),
|
|
"database_target_sha256": database_target,
|
|
},
|
|
ensure_ascii=True,
|
|
separators=(",", ":"),
|
|
)
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|