aboutsummaryrefslogtreecommitdiffstats
path: root/desmo_api/api.py
diff options
context:
space:
mode:
Diffstat (limited to 'desmo_api/api.py')
-rw-r--r--desmo_api/api.py112
1 files changed, 112 insertions, 0 deletions
diff --git a/desmo_api/api.py b/desmo_api/api.py
new file mode 100644
index 0000000..750904d
--- /dev/null
+++ b/desmo_api/api.py
@@ -0,0 +1,112 @@
+from fastapi import FastAPI
+from typing import Dict
+from .fsm import JailStateMachine
+import logging
+import sys
+import asyncio
+import statemachine.exceptions
+from contextlib import asynccontextmanager
+from .hcloud_dns import HCloudDNS
+import os
+from . import db, models
+import random
+import secrets
+
+logging.basicConfig(stream=sys.stderr, level=logging.INFO)
+logger = logging.getLogger("api")
+
+STATE_MACHINES: Dict[str, JailStateMachine] = {}
+
+DNS_CLIENT = HCloudDNS(os.environ["HCLOUD_DNS_KEY"])
+database = db.DB(os.environ["DATABASE_DSN"])
+
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ logger.info("Running migrations")
+ await database.migrate()
+ logger.info("Loading servers from database")
+ jails = await database.get_jails()
+ logger.info("Loaded %s jails from database", len(jails))
+ logger.info(jails)
+ for jail in jails:
+ logger.info("Loading server %s with state %s", jail.name, jail.state)
+ _fsm = JailStateMachine(DNS_CLIENT, database, jail.name)
+ _fsm.current_state_value = jail.state
+ _fsm.start_on_enter_task()
+ STATE_MACHINES[jail.name] = _fsm
+ yield
+ logger.info("Closing asyncio clients")
+ await DNS_CLIENT.close()
+ await database.close()
+ logger.info("Closed asyncio clients")
+
+
+app = FastAPI(lifespan=lifespan)
+
+
+@app.get("/")
+async def read_root():
+ return {"Hello": "World"}
+
+
+@app.post(
+ "/jails",
+ status_code=201,
+)
+async def create_jail(req: models.CreateJailRequest) -> models.FullJailInfo:
+ first_part = secrets.token_hex(2)
+ second_part = secrets.token_hex(2)
+ name = f"{req.name}-{first_part}-{second_part}"
+ ip = os.environ["NETWORK_PREFIX"] + "::" + first_part + ":" + second_part
+ hosts = os.environ["RUNNER_HOSTS"].split(",")
+ host = random.choice(hosts)
+ state = "uninitialized"
+
+ await database.insert_jail(name, host, ip, state)
+ for package in req.packages:
+ await database.insert_jail_package(name, package)
+
+ for i, command in enumerate(req.commands):
+ await database.insert_jail_command(name, command, i)
+
+ STATE_MACHINES[name] = JailStateMachine(DNS_CLIENT, database, name)
+ STATE_MACHINES[name].initialize()
+ await asyncio.sleep(1)
+ return models.FullJailInfo(
+ name=name,
+ state=state,
+ ip=ip,
+ host=host,
+ packages=req.packages,
+ commands=req.commands,
+ )
+
+
+@app.get("/jails/{name}")
+async def get_server(name: str) -> models.FullJailInfo | Dict[str, str]:
+ if name not in STATE_MACHINES:
+ return {"error": "server does not exist"}
+ jail = await database.get_jail(name)
+ packages = await database.get_jail_packages(name)
+ commands = await database.get_jail_commands(name)
+ return models.FullJailInfo(
+ name=jail.name,
+ state=jail.state,
+ ip=jail.ip,
+ host=jail.host,
+ packages=packages,
+ commands=commands,
+ )
+
+
+@app.delete("/jails/{name}")
+async def delete_server(name: str) -> Dict[str, str]:
+ if name not in STATE_MACHINES:
+ return {"error": "jail does not exist"}
+ try:
+ STATE_MACHINES[name].remove_jail()
+ except statemachine.exceptions.TransitionNotAllowed:
+ return {"error": "jail is not ready"}
+ await asyncio.sleep(1)
+ return {"status": "ok"}