Operationalize RC10 data retention
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Dry-run-first cleanup for old unreferenced derived/cache artifacts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from audit_data_operations import build_report
|
||||
from release_backup_guard import require_confirmation, verify_current_backup
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.db.session import SessionLocal
|
||||
|
||||
|
||||
CONFIRMATION = "DELETE_STORAGE_ARTIFACTS"
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--storage-root", type=Path)
|
||||
parser.add_argument("--minimum-age-days", type=int, default=7)
|
||||
parser.add_argument("--max-delete", type=int, default=25)
|
||||
parser.add_argument("--apply", action="store_true")
|
||||
parser.add_argument("--confirm")
|
||||
parser.add_argument("--backup-dir", type=Path)
|
||||
parser.add_argument("--backup-max-age-hours", type=float, default=24.0)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
if args.max_delete < 0:
|
||||
raise SystemExit("--max-delete must be greater than or equal to zero")
|
||||
storage_root = (args.storage_root or Path(get_settings().storage_root)).resolve()
|
||||
with SessionLocal() as db:
|
||||
report, candidates = build_report(
|
||||
storage_root,
|
||||
db,
|
||||
minimum_age_days=args.minimum_age_days,
|
||||
max_candidate_records=max(args.max_delete, 500),
|
||||
)
|
||||
|
||||
blocked_reason = None
|
||||
backup = None
|
||||
deleted: list[str] = []
|
||||
if args.apply:
|
||||
require_confirmation(args.confirm, CONFIRMATION)
|
||||
if args.backup_dir is None:
|
||||
raise RuntimeError("--backup-dir is required with --apply")
|
||||
backup = verify_current_backup(
|
||||
args.backup_dir,
|
||||
max_age_hours=args.backup_max_age_hours,
|
||||
)
|
||||
if len(candidates) > args.max_delete:
|
||||
blocked_reason = (
|
||||
f"candidate_count {len(candidates)} exceeds --max-delete {args.max_delete}; "
|
||||
"review the dry run and raise the explicit limit"
|
||||
)
|
||||
else:
|
||||
for candidate in candidates:
|
||||
candidate.path.unlink()
|
||||
deleted.append(candidate.relative_path)
|
||||
|
||||
payload = {
|
||||
"schema_version": 1,
|
||||
"mode": "apply" if args.apply else "dry-run",
|
||||
"storage_root": str(storage_root),
|
||||
"minimum_age_days": args.minimum_age_days,
|
||||
"max_delete": args.max_delete,
|
||||
"candidate_count": len(candidates),
|
||||
"candidate_bytes": sum(item.size_bytes for item in candidates),
|
||||
"candidates": [item.relative_path for item in candidates],
|
||||
"deleted_count": len(deleted),
|
||||
"deleted": deleted,
|
||||
"blocked_reason": blocked_reason,
|
||||
"protected_prefixes": report["cleanup"]["protected_prefixes"],
|
||||
"backup": (
|
||||
{
|
||||
"release_id": backup.release_id,
|
||||
"created_at": backup.created_at.isoformat(),
|
||||
"age_hours": round(backup.age_hours, 3),
|
||||
"git_commit": backup.git_commit,
|
||||
}
|
||||
if backup
|
||||
else None
|
||||
),
|
||||
}
|
||||
print(json.dumps(payload, indent=2, sort_keys=True))
|
||||
return 1 if blocked_reason else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user