feat: add ImageFind application and release pipelines
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Issue or revoke a short-lived API token used by live acceptance scripts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
def call(client: httpx.Client, method: str, path: str, **kwargs) -> 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)
|
||||
password = parser.add_mutually_exclusive_group()
|
||||
password.add_argument("--password")
|
||||
password.add_argument("--password-stdin", action="store_true")
|
||||
password.add_argument("--storage-state", type=Path)
|
||||
parser.add_argument("--token-file", type=Path, required=True)
|
||||
parser.add_argument("--id-file", type=Path, required=True)
|
||||
parser.add_argument("--name", default="ImageFind live acceptance")
|
||||
parser.add_argument("--revoke", action="store_true")
|
||||
args = parser.parse_args()
|
||||
token_revoke_ready = args.revoke and args.token_file.exists() and args.id_file.exists()
|
||||
if not token_revoke_ready and not (args.password or args.password_stdin or args.storage_state):
|
||||
parser.error("one of --password, --password-stdin or --storage-state is required")
|
||||
password_value = sys.stdin.readline().rstrip("\r\n") if args.password_stdin else args.password
|
||||
|
||||
client = httpx.Client(
|
||||
base_url=args.base_url.rstrip("/"),
|
||||
timeout=httpx.Timeout(30, connect=10),
|
||||
)
|
||||
if token_revoke_ready:
|
||||
client.headers["Authorization"] = (
|
||||
f"Bearer {args.token_file.read_text(encoding='utf-8').strip()}"
|
||||
)
|
||||
token_id = args.id_file.read_text(encoding="utf-8").strip()
|
||||
call(client, "DELETE", f"/api/v1/tokens/{token_id}")
|
||||
args.token_file.unlink(missing_ok=True)
|
||||
args.id_file.unlink(missing_ok=True)
|
||||
print(f"revoked live acceptance token {token_id}")
|
||||
return
|
||||
|
||||
if args.storage_state:
|
||||
state = json.loads(args.storage_state.read_text(encoding="utf-8"))
|
||||
for cookie in state.get("cookies", []):
|
||||
client.cookies.set(
|
||||
str(cookie["name"]),
|
||||
str(cookie["value"]),
|
||||
domain=str(cookie.get("domain") or ""),
|
||||
path=str(cookie.get("path") or "/"),
|
||||
)
|
||||
login = call(client, "GET", "/api/v1/auth/me").json()
|
||||
else:
|
||||
login = call(
|
||||
client,
|
||||
"POST",
|
||||
"/api/v1/auth/login",
|
||||
json={"password": password_value, "remember_device": False},
|
||||
).json()
|
||||
password_value = ""
|
||||
csrf = str(login.get("csrf_token") or "")
|
||||
if not csrf:
|
||||
raise AssertionError("login response omitted CSRF token")
|
||||
client.headers["X-CSRF-Token"] = csrf
|
||||
|
||||
if args.revoke:
|
||||
token_id = args.id_file.read_text(encoding="utf-8").strip()
|
||||
call(client, "DELETE", f"/api/v1/tokens/{token_id}")
|
||||
args.token_file.unlink(missing_ok=True)
|
||||
args.id_file.unlink(missing_ok=True)
|
||||
print(f"revoked live acceptance token {token_id}")
|
||||
return
|
||||
|
||||
result = call(
|
||||
client,
|
||||
"POST",
|
||||
"/api/v1/tokens",
|
||||
json={"name": args.name, "scopes": ["admin"]},
|
||||
).json()
|
||||
args.token_file.write_text(str(result["token"]), encoding="utf-8")
|
||||
args.id_file.write_text(str(result["id"]), encoding="utf-8")
|
||||
os.chmod(args.token_file, 0o600)
|
||||
os.chmod(args.id_file, 0o600)
|
||||
print(f"issued live acceptance token {result['id']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user