Odysseus v1.0
This commit is contained in:
145
scripts/odysseus-webhook
Executable file
145
scripts/odysseus-webhook
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
"""odysseus-webhook — shell wrapper for scheduled-task webhook tokens.
|
||||
|
||||
Tasks in the scheduled-task system can carry a `webhook_token`. Any
|
||||
HTTP POST to `/api/webhook/<token>` fires the task. This CLI lists,
|
||||
rotates, and revokes those tokens.
|
||||
|
||||
odysseus-webhook list # tasks that have a token
|
||||
odysseus-webhook show TASK_ID
|
||||
odysseus-webhook rotate TASK_ID # generate a fresh token
|
||||
odysseus-webhook revoke TASK_ID # remove the token
|
||||
odysseus-webhook url TASK_ID --base https://app.example.com
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
import os, sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "_lib"))
|
||||
from cli import quiet_logs, emit, fail, common_parser, run, REPO_ROOT as _REPO_ROOT
|
||||
quiet_logs()
|
||||
|
||||
import argparse, json, logging, os, secrets, sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from core.database import SessionLocal, ScheduledTask
|
||||
quiet_logs()
|
||||
except ModuleNotFoundError as e:
|
||||
sys.stderr.write(f"error: {e}\nhint: run from repo root with venv active.\n")
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def _summary(t: "ScheduledTask", reveal: bool = False) -> dict:
|
||||
tok = t.webhook_token or ""
|
||||
return {
|
||||
"task_id": t.id,
|
||||
"name": t.name,
|
||||
"status": t.status,
|
||||
"task_type": t.task_type,
|
||||
"webhook_token": tok if reveal else (tok[:6] + "…" + tok[-4:]) if tok else "",
|
||||
"has_token": bool(tok),
|
||||
}
|
||||
|
||||
|
||||
def cmd_list(args):
|
||||
db = SessionLocal()
|
||||
try:
|
||||
rows = db.query(ScheduledTask).filter(ScheduledTask.webhook_token.isnot(None)).all()
|
||||
emit([_summary(t, args.reveal) for t in rows], args)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def cmd_show(args):
|
||||
db = SessionLocal()
|
||||
try:
|
||||
t = db.get(ScheduledTask, args.id)
|
||||
if not t:
|
||||
fail(f"no task with id {args.id!r}")
|
||||
emit(_summary(t, args.reveal), args)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def cmd_rotate(args):
|
||||
db = SessionLocal()
|
||||
try:
|
||||
t = db.get(ScheduledTask, args.id)
|
||||
if not t:
|
||||
fail(f"no task with id {args.id!r}")
|
||||
# 32 bytes urlsafe → 43-char token, plenty of entropy.
|
||||
t.webhook_token = secrets.token_urlsafe(32)
|
||||
db.commit()
|
||||
emit({"ok": True, "task_id": t.id, "webhook_token": t.webhook_token}, args)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def cmd_revoke(args):
|
||||
db = SessionLocal()
|
||||
try:
|
||||
t = db.get(ScheduledTask, args.id)
|
||||
if not t:
|
||||
fail(f"no task with id {args.id!r}")
|
||||
old = t.webhook_token
|
||||
t.webhook_token = None
|
||||
db.commit()
|
||||
emit({"ok": True, "task_id": t.id, "revoked": bool(old)}, args)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def cmd_url(args):
|
||||
db = SessionLocal()
|
||||
try:
|
||||
t = db.get(ScheduledTask, args.id)
|
||||
if not t:
|
||||
fail(f"no task with id {args.id!r}")
|
||||
if not t.webhook_token:
|
||||
fail(f"task {args.id!r} has no webhook token (rotate one first)")
|
||||
base = (args.base or "http://localhost:7000").rstrip("/")
|
||||
url = f"{base}/api/webhook/{t.webhook_token}"
|
||||
emit({
|
||||
"task_id": t.id,
|
||||
"name": t.name,
|
||||
"url": url,
|
||||
"curl": f"curl -X POST {url}",
|
||||
}, args)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def _build_parser():
|
||||
common = argparse.ArgumentParser(add_help=False)
|
||||
common.add_argument("--pretty", action="store_true")
|
||||
p = argparse.ArgumentParser(prog="odysseus-webhook", parents=[common])
|
||||
sub = p.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
pl = sub.add_parser("list", parents=[common])
|
||||
pl.add_argument("--reveal", action="store_true", help="show full tokens")
|
||||
pl.set_defaults(func=cmd_list)
|
||||
|
||||
psh = sub.add_parser("show", parents=[common])
|
||||
psh.add_argument("id")
|
||||
psh.add_argument("--reveal", action="store_true")
|
||||
psh.set_defaults(func=cmd_show)
|
||||
|
||||
pr = sub.add_parser("rotate", parents=[common])
|
||||
pr.add_argument("id")
|
||||
pr.set_defaults(func=cmd_rotate)
|
||||
|
||||
prv = sub.add_parser("revoke", parents=[common])
|
||||
prv.add_argument("id")
|
||||
prv.set_defaults(func=cmd_revoke)
|
||||
|
||||
pu = sub.add_parser("url", parents=[common])
|
||||
pu.add_argument("id")
|
||||
pu.add_argument("--base", help="base URL (default http://localhost:7000)")
|
||||
pu.set_defaults(func=cmd_url)
|
||||
|
||||
return p
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run(_build_parser()))
|
||||
Reference in New Issue
Block a user