아바타 저장소 승격 계약을 완성
This commit is contained in:
parent
ac9b702688
commit
ccdcfcd2f5
36 changed files with 14734 additions and 222 deletions
136
scripts/validate-public-runtime-upload-manifest.py
Normal file
136
scripts/validate-public-runtime-upload-manifest.py
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
"""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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue