feat: add ImageFind application and release pipelines
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Remove narrowly prefixed records created by live acceptance runs."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
def call(client: httpx.Client, method: str, path: str, **kwargs: Any) -> httpx.Response:
|
||||
response = client.request(method, path, **kwargs)
|
||||
if response.is_error:
|
||||
raise RuntimeError(f"{method} {path} -> {response.status_code}: {response.text[:1000]}")
|
||||
return response
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--base-url", required=True)
|
||||
auth = parser.add_mutually_exclusive_group(required=True)
|
||||
auth.add_argument("--token-file", type=Path)
|
||||
auth.add_argument("--password")
|
||||
parser.add_argument("--prefix", required=True)
|
||||
parser.add_argument("--keep", action="append", default=[])
|
||||
parser.add_argument("--report", type=Path)
|
||||
args = parser.parse_args()
|
||||
if len(args.prefix) < 12 or not args.prefix.startswith("imagefind-e2e-"):
|
||||
raise ValueError("cleanup prefix must be a narrowly scoped imagefind-e2e-* value")
|
||||
headers = {}
|
||||
if args.token_file is not None:
|
||||
headers["Authorization"] = f"Bearer {args.token_file.read_text().strip()}"
|
||||
client = httpx.Client(
|
||||
base_url=args.base_url.rstrip("/"),
|
||||
headers=headers,
|
||||
timeout=httpx.Timeout(120, connect=10),
|
||||
)
|
||||
if args.password is not None:
|
||||
login = call(
|
||||
client,
|
||||
"POST",
|
||||
"/api/v1/auth/login",
|
||||
json={"password": args.password, "remember_device": False},
|
||||
).json()
|
||||
csrf = str(login.get("csrf_token") or "")
|
||||
if not csrf:
|
||||
raise AssertionError("login response omitted CSRF token")
|
||||
client.headers["X-CSRF-Token"] = csrf
|
||||
keep = set(args.keep)
|
||||
report: dict[str, Any] = {
|
||||
"prefix": args.prefix,
|
||||
"videos": [],
|
||||
"collections": [],
|
||||
"tags": [],
|
||||
"tag_groups": [],
|
||||
"people_hidden": [],
|
||||
}
|
||||
|
||||
rows = call(client, "GET", "/api/v1/videos?limit=500").json()
|
||||
for video in rows:
|
||||
filename = str(video.get("source_key") or "").rsplit("/", 1)[-1]
|
||||
if args.prefix not in str(video.get("source_key") or "") or filename in keep:
|
||||
continue
|
||||
deleted = call(client, "DELETE", f"/api/v1/videos/{video['id']}?delete_source=true").json()
|
||||
if trash_id := deleted.get("trash_id"):
|
||||
call(client, "DELETE", f"/api/v1/trash/{trash_id}")
|
||||
report["videos"].append({"id": video["id"], "filename": filename, **deleted})
|
||||
|
||||
for collection in call(client, "GET", "/api/v1/collections").json():
|
||||
if not str(collection.get("name") or "").startswith(args.prefix):
|
||||
continue
|
||||
detail = call(client, "GET", f"/api/v1/collections/{collection['id']}").json()
|
||||
if detail.get("video_count"):
|
||||
continue
|
||||
result = call(client, "DELETE", f"/api/v1/collections/{collection['id']}").json()
|
||||
report["collections"].append(result)
|
||||
|
||||
for group in call(client, "GET", "/api/v1/tag-groups").json():
|
||||
if not str(group.get("name") or "").startswith(args.prefix):
|
||||
continue
|
||||
tags = call(client, "GET", f"/api/v1/tags?group_id={group['id']}").json()
|
||||
if any(int(tag.get("video_count") or 0) for tag in tags):
|
||||
continue
|
||||
for tag in tags:
|
||||
call(client, "DELETE", f"/api/v1/tags/{tag['id']}")
|
||||
report["tags"].append(tag["id"])
|
||||
call(client, "DELETE", f"/api/v1/tag-groups/{group['id']}")
|
||||
report["tag_groups"].append(group["id"])
|
||||
|
||||
for source in call(client, "GET", "/api/v1/sources").json():
|
||||
for item in call(client, "GET", f"/api/v1/trash?source_id={source['id']}").json():
|
||||
if args.prefix in str(item.get("display_name") or ""):
|
||||
call(client, "DELETE", f"/api/v1/trash/{item['id']}")
|
||||
|
||||
# Named people are deliberately preserved when their last face is removed.
|
||||
# There is no destructive people endpoint, so hide narrowly prefixed test
|
||||
# identities after their fixture videos have been deleted.
|
||||
for person in call(client, "GET", "/api/v1/people").json():
|
||||
name = str(person.get("name") or "")
|
||||
if not name.startswith(args.prefix) or person.get("hidden"):
|
||||
continue
|
||||
call(client, "PATCH", f"/api/v1/people/{person['id']}", json={"name": name, "hidden": True})
|
||||
report["people_hidden"].append(person["id"])
|
||||
|
||||
encoded = json.dumps(report, ensure_ascii=False, indent=2) + "\n"
|
||||
if args.report:
|
||||
args.report.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.report.write_text(encoded)
|
||||
print(encoded, end="")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user