# retoor <retoor@molodetz.nl>
import json
from datetime import datetime, timezone
from devplacepy.database import db, get_table
from devplacepy.utils import generate_uid, make_combined_slug
ST_CREATED = "created"
ST_STARTING = "starting"
ST_RUNNING = "running"
ST_STOPPED = "stopped"
ST_PAUSED = "paused"
ST_CRASHED = "crashed"
ST_RESTARTING = "restarting"
ST_REMOVING = "removing"
ST_REMOVED = "removed"
DESIRED_RUNNING = "running"
DESIRED_STOPPED = "stopped"
DESIRED_PAUSED = "paused"
RESTART_POLICIES = ("never", "always", "on-failure", "unless-stopped")
METRICS_RING = 720
def now() -> str:
return datetime.now(timezone.utc).isoformat()
def _exists(table: str) -> bool:
return table in db.tables
# ---------------- instances ----------------
def create_instance(row: dict) -> dict:
uid = generate_uid()
base = {
"uid": uid, "slug": make_combined_slug(row.get("name", "instance"), uid),
"container_id": "", "status": ST_CREATED, "exit_code": 0, "restart_count": 0,
"ingress_slug": "", "ingress_port": 0,
"started_at": "", "stopped_at": "", "created_at": now(), "updated_at": now(),
}
base.update(row)
base["uid"] = uid
get_table("instances").insert(base)
return get_instance(uid)
def get_instance(uid: str):
table = get_table("instances")
if not _exists("instances"):
return None
return table.find_one(uid=uid) or table.find_one(slug=uid)
def list_instances(project_uid: str = None) -> list:
if not _exists("instances"):
return []
filters = {}
if project_uid:
filters["project_uid"] = project_uid
return sorted(get_table("instances").find(**filters),
key=lambda r: r.get("created_at", ""), reverse=True)
def all_instances() -> list:
if not _exists("instances"):
return []
return list(get_table("instances").find())
def find_instance_by_ingress(slug: str):
if not slug or not _exists("instances"):
return None
matches = list(get_table("instances").find(ingress_slug=slug))
for instance in matches:
if instance.get("status") == ST_RUNNING:
return instance
return matches[0] if matches else None
def update_instance(uid: str, changes: dict) -> None:
get_table("instances").update({"uid": uid, "updated_at": now(), **changes}, ["uid"])
def delete_instance(uid: str) -> None:
get_table("instances").delete(uid=uid)
# ---------------- events / metrics / schedules ----------------
def record_event(instance: dict, event: str, actor_kind: str, actor_id: str, detail: dict = None) -> None:
get_table("instance_events").insert({
"uid": generate_uid(), "instance_uid": instance["uid"], "project_uid": instance.get("project_uid", ""),
"event": event, "actor_kind": actor_kind, "actor_id": actor_id or "",
"detail": json.dumps(detail or {}), "created_at": now(),
})
def list_events(instance_uid: str, limit: int = 100) -> list:
if not _exists("instance_events"):
return []
rows = sorted(get_table("instance_events").find(instance_uid=instance_uid),
key=lambda r: r.get("created_at", ""), reverse=True)
return rows[:limit]
def insert_metric(instance_uid: str, sample) -> None:
table = get_table("instance_metrics")
table.insert({
"uid": generate_uid(), "instance_uid": instance_uid, "ts": now(),
"cpu_pct": sample.cpu_pct, "mem_bytes": sample.mem_bytes, "mem_pct": sample.mem_pct,
"net_rx": sample.net_rx, "net_tx": sample.net_tx,
"blk_read": sample.blk_read, "blk_write": sample.blk_write,
})
rows = sorted(table.find(instance_uid=instance_uid), key=lambda r: r.get("ts", ""))
if len(rows) > METRICS_RING:
for old in rows[:len(rows) - METRICS_RING]:
table.delete(uid=old["uid"])
def recent_metrics(instance_uid: str, limit: int = 120) -> list:
if not _exists("instance_metrics"):
return []
rows = sorted(get_table("instance_metrics").find(instance_uid=instance_uid),
key=lambda r: r.get("ts", ""))
return rows[-limit:]
def create_schedule(instance: dict, action: str, schedule_columns: dict, next_run_at: str) -> dict:
uid = generate_uid()
get_table("instance_schedules").insert({
"uid": uid, "instance_uid": instance["uid"], "project_uid": instance.get("project_uid", ""),
"action": action, "schedule_json": json.dumps(schedule_columns), "enabled": 1,
"next_run_at": next_run_at, "last_run_at": "", "run_count": 0,
"created_at": now(), "updated_at": now(),
})
return get_table("instance_schedules").find_one(uid=uid)
def list_schedules(instance_uid: str) -> list:
if not _exists("instance_schedules"):
return []
return list(get_table("instance_schedules").find(instance_uid=instance_uid))
def due_schedules(now_iso: str) -> list:
if not _exists("instance_schedules"):
return []
return [r for r in get_table("instance_schedules").find(enabled=1)
if r.get("next_run_at") and r["next_run_at"] <= now_iso]
def update_schedule(uid: str, changes: dict) -> None:
get_table("instance_schedules").update({"uid": uid, "updated_at": now(), **changes}, ["uid"])
def delete_schedule(uid: str) -> None:
get_table("instance_schedules").delete(uid=uid)